Scala:
val x: Int = 1
Java:
Integer x = 1;
C#:
int i = 1;
Тип это некоторое ограничение, накладываемое на какое-то значение и на то как это значение может использоваться.
val a: Int = 1
a > 1 // Ок
a == false // ??
val b: Boolean = false
b || true // Ок
b > 1 //??
b.someMethod() //??
Система типов в языке - это набор базовых типов, правила и механизмы по описанию новых типов и по применению различных типов.
Примеры: Java, C#, TypeScript, C/C++, Rust, Scala и др.
Примеры: JavaScript, Python, PHP, Ruby, Perl, Clojure и др.
(scala.Dynamic)
(JavaScript - Flow/TypeScript, Python - PEP 484 introduced type hints)
Java, C#, C++, Scala, Python и др.
JavaScript, PHP и др.
Java, C#, C++, Scala и др.
JavaScrip, PHP и др.
Большинство программистов считают что в большинстве случаев лучше язык со статической и строгой типизацией при разработке программ
Ниша динамических и/или слабо типизированных языков - небольших и простые приложения (скрипты, примитивные приложения, обучение)
Проблемы для языка:
Проблемы для разработчика:
Решение проблем разработчиков в Scala:
(однако создателям библиотек приходиться попотеть)
((также как и создателям языка))
По простому - там где типы могут быть однозначно просчитаны компилятором - их можно не писать
Пример:
val x = "Hello world" // val x: String
def someFunction() = 42 // def someFunction(): Int
val y = {
if(randomBoolean) {
Cat // наследует Animal
} else {
Dog // наследует Animal
}
}
// val y: Animal
Гибко и удобно использовать систему типов позволяет полиморфизм
Принципиальная возможность для одного и того же кода обрабатывать данные разных типов
Зачастую ставятся определенные условия, которым должны соответствовать типы
В каждом конкретном языке - набор отдельных фич которые дают ту или иную степерь полиморфизма
(subtyping | наследование)
(generics)
(перегрузка функций/операторов, авто приведение типов Int -> Long, в scala - implicit conversion)
если для некоторого типа A выполняется какое-то правило
foo(A.value) == OK
то тогда для любого типа B, являющегося подтипом типа A
B <: A
также должно выполняться это правило
foo(B.value) == OK
// для обозначения подтипа в Scala используется "<:"
// Subtype <: Supertype
class Animal
class Cat extends Animal
// Cat <: Animal
class Dog extends Animal
// Dog <: Animal
def foo(x: Animal) = ???
foo(new Cat()) // OK if Cat <: Animal
foo(new Dog()) // OK if Dog <: Animal
foo("Hello world") // compile error
Any - надтип (supertype) всех типов в Scala
Использование:
def foo(x: Any) = ???
val a = if (randomBoolean) {
"Hello world"
} else {
42
}
// val a: Any - Почему..?
AnyVal - тип оберток над значениями
Компилятор с такими типами обходится по особенному - стремится оптимизировать так, чтобы обертка не создавала реальный объект в памяти
Использование:Пример создания типа от AnyVal
// Обёртка над типом для добавления новых функций
class Age(val underlying: Int) extends AnyVal {
def foo: Wrapper = new Wrapper(underlying * 19)
}
class Meter(val value: Double) extends AnyVal {
def +(m: Meter): Meter = new Meter(value + m.value)
}
// по сути, после компиляции здесь просто будут операции над Double
val x = new Meter(3.4)
val y = new Meter(4.3)
val z = x + y
// Помогает работать с примитивными типами как с чем-то осмысленным
case class Age(val value: Int) extends AnyVal
def isOlderThen18(age: Age): Boolean = age.value >= 18
AnyRef - надтип для всех ссылочных типов.
Использование:
Null - подтип для всех подтипов AnyRef.
Т.е. для любого X <: AnyRef справедливо Null <: X
Это делается автоматически на уровне компилятора
Null
val a = if (randomBoolean) {
"Hello world" // String
} else {
null // Null
}
// val a: String - потому что Null <: String
Nothing
val a = if (randomBoolean) {
42 // Int
} else {
??? // Nothing
}
// val a: Int - потому что Nothing <: Int
sealed abstract class Option[+A] { ... }
final case class Some[+A](x: A) extends Option[A] {
def isEmpty = false
def get = x
}
case object None extends Option[Nothing] {
def isEmpty = true
def get = throw new NoSuchElementException("None.get")
}
Полиморфизм подтипов (subtyping) - имеет пересечения и взаимодействие с другими видами полиморфизма.
Точнее ряд фич системы типов Scala, которые относятся к полиморфизмам другого типа, учитывают правила, которые справедливы для подтипов.
def someMethod[T](x: T) = ???
class List[A] {
def add(a: A) = ???
}
trait Box[A] {
val value: A
def compare[B](other: Box[B]): Boolean
}
Нельзя задать переменную с типом List[A]:
val x: List[A] // compile error
Можно задать переменную с типом, сконструированным с помощью дженерика:
val x: List[Int] // OK - тип списка интов
List[A] - множество типов различных типов
Конструктор типа
Тип Х -> Конструктор List[_] -> конструируем тип List[X]
Вариантность
Ковариантный (covariance) - List[+A]
Ковариантный (covariance) - List[+A]
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
}
class Dog extends Animal {
def makeSound = "bark"
}
def fooWithCovarianceParam(items: List[Animal]): Unit = {
//some side effect
}
def fooWithInvariantParam(items: Array[Animal]): Unit = {
//some side effect
}
fooWithCovarianceParam(List(new Dog())) // OK, так как List[+A] ковариантен
fooWithInvariantParam(Array(new Dog()))// ERROR, так как тип Array[A] инвариантен
Проблема ковариантности
//допустим, MutableList - ковариантен. Помимо этого MutableList - мутабелен
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
def jump = "jump"
}
class Dog extends Animal {
def makeSound = "bark"
}
def addDog(xs: MutableList[Animal]) = {
xs += new Dog()
}
val cats = MutableList[Cat](new Cat(), new Cat())
val horror = addDog(cats)
cats.forEach(cat => cat.jump) //error
Проблема ковариантности
trait Box[+A] {
def contains(a: A): Boolean
}
// Потенциальная опасность получить RunTimeError - Почему?
trait Animal
case class Cat(name: String) extends Animal
case class Dog(age: Int) extends Animal
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains(a: Cat): Boolean = value.name == a.name
}
val animalBox: Box[Animal] = catBox
animalBox.contains(Dog(12)) // RunTimeError
trait Box[+A] {
def contains(a: A): Boolean
}
// compile error (Covariant type A occurs in contravariant position in type A of value a)
Решение
trait Box[+A] {
val value: A
def contains[B >: A](a: B): Boolean
}
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains(a: Cat): Boolean = value.name == a.name // compile error
}
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains[B >: Cat](a: B): Boolean = ???
}
Контравариантность (contravariance) - Vet[-A]
Контрвариантность (contravariance)
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
}
class Dog extends Animal {
def makeSound = "bark"
}
class Vet[-A]
def treatDogs(vet: Vet[Dog]) {}
val commonVet = new Vet[Animal]()
treatDogs(commonVet) // OK, ошибки не будет, так как Vet Контрвариантный
Mixins, как правило, используются для добавления дополнительной функциональности
Для использования mixin используется ключевое слово with
trait Human {
def say(): Unit = ???
}
trait Wizard {
def doMagic(): Unit = ???
}
class HumanWithMagic extends Human with Wizard
val a: Human with Wizard = new HumanWithMagic
a.say()
a.doMagic()
Помогают накладывать ограничение на использование mixin
trait Animal
trait Car
trait CanJump { self: Animal =>
def jump: Unit = ???
}
class Cat extends Animal with CanJump
class Kia extends Car with CanJump // compile error
Тип вида A & B & C & ... интерпретируется как все эти типы в один момент
trait Foo
def foo(): Unit
trait Bar[A]
def setBar(value: A): Unit
def fooBar(x: Foo & Bar[String]): Unit =
x.foo()
x.setBar("Hello")
Тип вида A | B | C | ... может быть одним из перечисленных типов в один момент
Для обработки такого типа необходимо использовать pattern matching
case class Username(name: String)
case class Password(hash: Hash)
def help(id: Username | Password) =
val user = id match
case Username(name) => ???
case Password(hash) => ???
| в отличии от with коммутативная операция
A with B != B with A
A | B == B | A