Система типов в Scala

Гавриков Антон

Что такое тип?

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 и др.

  • Языки использующие слабую типизацию

    JavaScrip, PHP и др.

  • Виды систем типов

    Явная/неявная типизация

  • И еще одно условное деление
  • Определяется тем, что тип новых переменных, функций и аргументов функций нужно задавать явно
  • Виды систем типов

    Явная/неявная типизация

    • Языки использующие явную типизацию

      Java, C#, C++, Scala и др.

    • Языки использующие неявную типизацию

      JavaScrip, PHP и др.

    Виды систем типов

    Виды систем типов

    Что лучше..?

    Виды систем типов

    Что лучше?

    Большинство программистов считают что в большинстве случаев лучше язык со статической и строгой типизацией при разработке программ

    Ниша динамических и/или слабо типизированных языков - небольших и простые приложения (скрипты, примитивные приложения, обучение)

    Виды систем типов

    Статическая строгая типизация

    Проблемы для языка:

  • Сложный компилятор (дорого, багоемко, неповоротливо)
  • Долгая компиляция
  • Проблемы для разработчика:

  • Нужно описывать типы
  • Сложнее использовать
  • Виды систем типов

    Статическая строгая типизация

    Решение проблем разработчиков в Scala:

  • Вывод типов чтобы их писать по минимуму
  • Мощная система типов позволяющая использовать типы легко

    (однако создателям библиотек приходиться попотеть)

    ((также как и создателям языка))

  • Система типов в Scala

    Система типов в Scala

    Вывод типов

    По простому - там где типы могут быть однозначно просчитаны компилятором - их можно не писать

    Система типов в 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
                    

    Система типов в Scala

    Гибко и удобно использовать систему типов позволяет полиморфизм

    Что такое полиморфизм..?

    Система типов в Scala

    Полиморфизм

    Принципиальная возможность для одного и того же кода обрабатывать данные разных типов

    Зачастую ставятся определенные условия, которым должны соответствовать типы

    В каждом конкретном языке - набор отдельных фич которые дают ту или иную степерь полиморфизма

    Полиморфизм

    Виды

  • полиморфизм подтипов

    (subtyping | наследование)

  • параметрический полиморфизм

    (generics)

  • ad-hoc полиморфизм

    (перегрузка функций/операторов, авто приведение типов Int -> Long, в scala - implicit conversion, pattern typeclass)

  • Полиморфизм подтипов

  • Полифморфизм подтипов - наиболее простой и распространенный
  • Определены базовые правила работы подтипов
  • Новые типы задаются через определение новых class/trait
  • Отношения задаются в основном через наследование
  • Полиморфизм подтипов

  • Буква L в SOLID
  • Liskov Substitution Principle (Принцип подстановки Барбары Лисков)
  • Полиморфизм подтипов

    Liskov Substitution Principle

    Формально:

    если для некоторого типа A выполняется какое-то правило

    foo(A.value) == OK

    то тогда для любого типа B, являющегося подтипом типа A

    B <: A

    также должно выполняться это правило

    foo(B.value) == OK

    Полиморфизм подтипов

    Liskov Substitution Principle

    
    
    // для обозначения подтипа в 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 - тип оберток над значениями

    Компилятор с такими типами обходится по особенному - стремится оптимизировать так чтобы обертка не создавала реальный объект в памяти

    Использование:
  • примитивы - Int, Long, Double, и т.д.
  • Обертки добавляющие методы или дополнительный тип
  • Unit
  • Полиморфизм подтипов AnyVal

    Пример создания типа от AnyVal

    
    class Wrapper(val underlying: Int) extends AnyVal {
        def foo: Wrapper = new Wrapper(underlying * 19)
    }
                    

    Полиморфизм подтипов

    Unit

  • Тип с единственным значением "()"
  • Используется в местах где в C#/Java стоял бы "void"
  • Т.е. тогда, когда нам не интересно значение выполнения функции, но нужно какое-то пустое значение
  • Обычно связано с сайд-эффектами (печать в консоль, запись в базу, мутацию какого-то внешнего объекта, и т.д.)
  • Значения "()" не существует во время исполнения (абстракция на уровне компилятора)
  • Полиморфизм подтипов

    Переходим к AnyRef

    Полиморфизм подтипов

    AnyRef - надтип для всех остальных типов данных, т.е. для всех ссылочных типов.

  • В jvm - соответствует java.lang.Object, т.е. объектам в heap
  • Использование:

  • В практике особо не используется
  • Но для полноты системы типов нужен
  • Полиморфизм подтипов

    Null - подтип для всех подтипов AnyRef.

    Т.е. для любого X <: AnyRef справедливо Null <: X

    Это делается автоматически на уровне компилятора

  • литерал null имеет этот тип
  • из-за того что Null это подтип любого подтипа AnyRef - мы можем в любое место которое принимает любой ссылочный тип - поставить null
  • также это позволяет работать системе вывода типов при проставлении null
  • Полиморфизм подтипов

    Null

    
    val a = if (randomBoolean) {
        "Hello world" // String
    } else {
        null // Null
    }
    
    // val a: String - потому что Null <: String                
                    

    Полиморфизм подтипов

  • Nothing - подтип всех типов, также называемый bottom type.

  • Т.е. для любого X справедливо Nothing <: X
  • Тип у которого нет значениний
  • Нужен для полноты системы типов
  • В некоторых случаях удобно использовать в качестве подтипа любого типа
  • Выражение в результате которого выбрасывается exception имеет тип Nothing (частный случай - "???")
  • Полиморфизм подтипов

    Nothing

    
    val a = if (randomBoolean) {
        42 // Int
    } else {
        ??? // Nothing
    }
    
    // val a: Int - потому что Nothing <: Int                
                    

    Полиморфизм подтипов

    Подведем итоги

    Полиморфизм подтипов

    Полиморфизм подтипов (subtyping) - имеет пересечения и взаимодействие с другими видами полиморфизма.

    Точнее ряд фич системы типов Scala, которые относятся к полиморфизмам другого типа, учитывают правила которые справедливы для подтипов

    Парам. полиморфизм

  • Второй по распространенности и используемости вид полиморфизма
  • Его вы скорее всего знаете в виде дженериков и параметров типов при объявлении классов и методов
  • 
    def someMethod[T](x: T) = ???
    
    class List[A] {
        def add(a: A) = ???
    }
                    

    Парам. полиморфизм

    является ли List[A] - типом?

    Парам. полиморфизм

    является ли List[A] типом? - Нет

    Нельзя задать переменную с типом List[A]:

    
            val x: List[A] // compile error
                            

    Можно задать переменную с типом, сконструированным с помощью дженерика:

    
            val x: List[Int] // OK - тип списка интов
                    

    Парам. полиморфизм

    List[A] - задает как бы семейство или множество типов различных типов

    Можно на дженерик также смотреть как на конструктор типа

    допустим у нас есть некий тип X и мы с помощью такого конструктора List[_] можем сконструировать тип List[X]

    Пример: берем тип Int, берем конструктор типа List[_] - конструируем конкретный тип List[Int]

    Парам. полиморфизм

    List[A] в иерархии подтипов

    Парам. полиморфизм

    Имеют ли между собой отношения подтипов разные типы семейства типов List[A]?

    
    // если Int <: Any то можно ли сказать что List[Int] <: List[Any]
    
    val x: List[Int] 
    
    val a: List[Any] = x // OK?
                    

    Парам. полиморфизм

    Имеют ли между собой отношения подтипов разные типы семейства типов List[A]?

    Нет

    Парам. полиморфизм

    Подобные отношения среди типов одного семейства называются вариантностью

  • Инвариантный (invariant) дженерик - по умолчанию
  • Ковариантный (covariance) - Producer[+A]
  • Контравариантный (contravariance) - Consumer[-A]
  • Парам. полиморфизм

    Ковариантный (covariance) - Producer[+A]

    Парам. полиморфизм

    Ковариантный (covariance) - Producer[+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
                    

    Парам. полиморфизм

    Контравариантный (contravariance) - Consumer[-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 контрвнтный