Параметрический полиморфизм в Scala

IntBox


case class IntBox(
    value: Int
)

scala> IntBox(5)
res0: IntBox = IntBox(5)

StringBox


case class StringBox(
    value: String
)

scala> StringBox("hello")
res0: StringBox = StringBox(hello)

BooleanBox


case class BooleanBox(
    value: Boolean
)

scala> BooleanBox(true)
res1: BooleanBox = BooleanBox(true)

...

Box[T]


case class Box[T](
    value: T
)

scala> Box[Int](5)
res2: Box[Int] = Box(5)

scala> Box[String]("hello")
res3: Box[String] = Box(hello)
scala> Box[String](5)

<console>:14: error: type mismatch;
 found   : Int(5)
 required: String
scala> Box(5)
res1: Box[Int] = Box(5)

scala> Box("hello")
res2: Box[String] = Box(hello)
scala> val x = Box(5)
x: Box[Int] = Box(5)

scala> x.value
res3: Int = 5

scala> x.value + 1
res4: Int = 6

scala> x.value.substring(1)
<console>:15: error: value substring is not a member of Int
Option[T] - 0 или 1 элемент типа Т

List[T] - список значений типа T

Несколько параметров


case class Box2[A, B](
    value1: A,
    value2: B
)

scala> Box2(5, "hello")
res6: Box2[Int,String] = Box2(5,hello)
(A, B) - пара элементов типов A и B

Either[A, B] - либо значение типа A, либо значение типа B

Map[K, V] - отображение ключ (K) - значение (V)

A => B - функция из объекта типа A в объект типа B
Box(0.5): ???

Box2(true, "hello"): ???

Box(Box(5)): ???

Box(Box2(0.5, true)): ???

Box2(Box(3), Box2("hello", true)): ???
Box(0.5): Box[Double]

Box2(true, "hello"): ???

Box(Box(5)): ???

Box(Box2(0.5, true)): ???

Box2(Box(3), Box2("hello", true)): ???
Box(0.5): Box[Double]

Box2(true, "hello"): Box2[Boolean,String]

Box(Box(5)): ???

Box(Box2(0.5, true)): ???

Box2(Box(3), Box2("hello", true)): ???
Box(0.5): Box[Double]

Box2(true, "hello"): Box2[Boolean,String]

Box(Box(5)): Box[Box[Int]]

Box(Box2(0.5, true)): ???

Box2(Box(3), Box2("hello", true)): ???
Box(0.5): Box[Double]

Box2(true, "hello"): Box2[Boolean,String]

Box(Box(5)): Box[Box[Int]]

Box(Box2(0.5, true)):
  Box[Box2[Double,Boolean]]

Box2(Box(3), Box2("hello", true)): ???
Box(0.5): Box[Double]

Box2(true, "hello"): Box2[Boolean,String]

Box(Box(5)): Box[Box[Int]]

Box(Box2(0.5, true)):
  Box[Box2[Double,Boolean]]

Box2(Box(3), Box2("hello", true)):
  Box2[Box[Int],Box2[String,Boolean]]

Параметры функций


def toBox2[A, B](
    first: Box[A],
    second: Box[B]
): Box2[A, B] =
  Box2(first.value, second.value)

scala> toBox2(Box("hello"), Box(5))
res7: Box2[String,Int] = Box2(hello,5)

Область видимости


case class Box2[A, B](
    value1: A,
    value2: B
) {
  def withNewValue1(
      newValue: A
  ): Box2[A, B] =
    Box2(newValue, value2)
}
case class Box2[A, B](
    value1: A,
    value2: B
) {
  def withNewValue1[C](
      newValue: C
  ): Box2[C, B] =
    Box2(newValue, value2)
}

Конкретный параметр


def sum(box: Box2[Int, Int]): Box[Int] =
  Box(box.value1 + box.value2)

scala> sum(Box2(1, 2))
res8: Box[Int] = Box(3)

Игнорируем параметр


def getFirst[T](box: Box2[T, _]): T =
  box.value1

scala> getFirst(Box2("hello", 4))
res9: String = hello
Написать функцию swap, которая принимает пару (Box2) и возвращает ее с переставленными местами элементами

???
def swap[A, B](
    box: Box2[A, B]
): Box2[B, A] =
  Box2(box.value2, box.value1)

scala> swap(Box2("hello", 5))
res0: Box2[Int,String] = Box2(5,hello)
Написать функцию sum2, которая принимает две пары, у которых первые элементы Double-ы, и возвращает их сумму

???
def sum2(
    box1: Box2[Double, _]
    box2: Box2[Double, _]
): Double =
  box1.value1 + box2.value1

scala> sum2(
  Box2(0.5, "hello"), Box2(0.7, true)
)
res2: Double = 1.2
Написать метод класса Box - map, который принимает функцию и возвращает новый Box с результатом применения этой функции к хранящемуся в старом Box значению

???
case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] =
    Box(f(value))
}

scala> Box("hello").map(_.length)
res3: Box[Int] = Box(5)

Upper Bounds

trait Animal {
  def name: String
}

case class Cat(
    override val name: String
) extends Animal

case class Dog(
    override val name: String
) extends Animal
def shortestName(
    animals: List[Animal]
): Option[Animal] =
  animals.sortBy(_.name.length).headOption
def sayMeow(cat: Cat): String =
  cat.name + " says meow."

val cats: List[Cat] = List(
  Cat("Garfield"),
  Cat("Lucy"),
  Cat("Kuzya")
)
val cats: List[Cat] = ...

scala> val s = shortestName(cats)
s: Option[Animal] = Some(Cat(Lucy))

scala> s.map(cat => sayMeow(cat))
<console>:19: error: type mismatch;
 found   : Animal
 required: Cat
scala> s.map { cat =>
  sayMeow(cat.asInstanceOf[Cat])
}

res7: Option[String] =
  Some(Lucy says meow.)

Грязный код.
Потенциальные ошибки.
scala> val s =
  shortestName(List(Dog("Goofy")))
s: Option[Animal] = Some(Dog(Goofy))

scala> s.map { cat =>
  sayMeow(cat.asInstanceOf[Cat])
}

java.lang.ClassCastException:
  Dog cannot be cast to Cat

Ошибка во время исполнения.
def shortestName[T](
    animals: List[T]
): Option[T] =
  animals.sortBy(_.name.length).headOption

<console>:11: error: value name is not a member of type parameter T

Upper Bound


def shortestName[T <: Animal](
    animals: List[T]
): Option[T] =
  animals.sortBy(_.name.length).headOption
scala> val s = shortestName(cats)
s: Option[Cat] = Some(Cat(Lucy))

scala> s.map(sayMeow)
res11: Option[String] =
  Some(Lucy says meow.)
scala> val s =
  shortestName(List(Dog("Goofy")))
s: Option[Dog] = Some(Dog(Goofy))

scala> s.map(sayMeow)
<console>:20: error: type mismatch;
 found   : Cat => String
 required: Dog => ?

Lower Bounds

def addToCats(
    cats: List[Cat],
    other: List[Animal]
): List[Animal] =
  cats ++ other

scala> addToCats(
  List(Cat("Lucy")),
  List(Cat("Kuzya"))
)
res13: List[Animal] =
  List(Cat(Lucy), Cat(Kuzya))
def addToCats[T](
    cats: List[Cat],
    other: List[T]
): List[T] =
  cats ++ other

<console>:15: error: type mismatch;
 found   : List[Any]
 required: List[T]

Lower Bound


def addToCats[T >: Cat](
    cats: List[Cat],
    other: List[T]
): List[T] =
  cats ++ other
scala> addToCats(
  List(Cat("Lucy")),
  List(Cat("Kuzya"))
)

res8: List[Cat] =
  List(Cat(Lucy), Cat(Kuzya))
scala> addToCats(
  List(Cat("Lucy")),
  List(Dog("Goofy"))
)

res9: List[Animal] =
  List(Cat(Lucy), Dog(Goofy))

Type Class
(в следующих лекциях)


def func[T: TypeClass] = ...

Несколько ограничений одновременно


def func[T >: Cat <: Animal with CanMove: Decoder: Encoder] = ...

Variance

def getName(box: Box[Animal]): String =
  box.value.name

scala> val box = Box(Cat("Kuzya"))
box: Box[Cat] = Box(Cat(Kuzya))
scala> getName(box)
<console>:19: error: type mismatch;
 found   : Box[Cat]
 required: Box[Animal]
Note: Cat <: Animal, but class Box is invariant in type T.

Invariance


A =:= B => F[A] =:= F[B]

case class Box[T](value: T)

Box[Animal] =:= Box[Animal]

Box[Cat] =:= Box[Cat]

Box[Cat] <: Box[Animal] - неверно!

Covariance


A <: B => F[A] <: F[B]

case class Box[+T](value: T)

Cat <: Animal => Box[Cat] <: Box[Animal]

scala> getName(box)
res2: String = Kuzya
Почему всегда не использовать ковариантность?

Contravariance


B <: A => F[A] <: F[B]

trait Printer[-T] {
  def print(value: T): String
}
val catPrinter = new Printer[Cat] {
  override def print(cat: Cat): String =
    cat.name + " is a cat!"
}

val animalPrinter = new Printer[Animal] {
  override def print(animal: Animal): String =
    animal match {
      case Cat(name) => name + " is a cat!"
      case Dog(name) => name + " is a dog!"
    }
}
Printer[Cat] - может печатать только котов

Printer[Animal] - может печатать любых животных

Cat <: Animal =>
  Printer[Animal] <: Printer[Cat]
def printCat(
    cat: Cat,
    printer: Printer[Cat]
): String =
  printer.print(cat)

scala> printCat(
  Cat("Garfield"),
  animalPrinter
)
res3: String = Garfield is a cat!

Position variance


trait Printer[+T] {
  def print(value: T): String
}

Что произойдет?
trait Printer[+T] {
  def print(value: T): String
}

<console>:12: error: covariant type T occurs in contravariant position in type T of value value
case class Box[-T](value: T)

<console>:11: error: contravariant type T occurs in covariant position in type => T of value value
trait Function[?A, ?B] {
  def apply(argument: A): B
}

val name = new Function[Animal, String] {
  override def apply(
      argument: Animal
  ): String =
    argument.name
}

scala> name(Cat("Kuzya"))
res15: String = Kuzya
trait Function[-A, +B] {
  def apply(argument: A): B
}
trait GetSet[?T] {
  def get: T

  def set(value: T): Unit
}
trait GetSet[T] {
  def get: T

  def set(value: T): Unit
}
trait Function[-A, +B] {
  def apply(argument: A): B

  def andThen[C](
      f: Function[B, C]
  ): Function[A, C] = { argument: A =>
    f.apply(apply(argument))
  }
}

scala> val f = name.andThen(_.length)
scala> f(Cat("Garfield"))
res0: Int = 8

Kind

scala> def incorrect(box: Box) = box

<console>:13: error: class Box takes type parameters

Box не является типом!

Конкретный тип: *


  • Int
  • String
  • Box[Int]
  • Box[Box[String]]
  • List[Double]
  • Box2[Double, String]
  • Map[String, Int]
  • Конструктор типа с одним параметром: * -> *


  • Box
  • List
  • Option
  • Box2[Int, *]
  • Map[*, Double]
  • конструктор типа с двумя параметрами: * -> * -> *


  • Box2
  • Map
  • Either
  • (* -> *) -> *


    ???

    Higher Kinds


    (* -> *) -> * (конструктор типа, который принимает в качестве аргумента другой конструктор типа)

    Флаг компилятора:  -language:higherKinds

    case class HigherKindedBox[T[_]](
        value: T[Int]
    )

    scala> HigherKindedBox(Box(5))
    res10: HigherKindedBox[Box] =
      HigherKindedBox(Box(5))

    scala> HigherKindedBox(List(5, 6, 7))
    res11: HigherKindedBox[List] =
      HigherKindedBox(List(5, 6, 7))
    scala> HigherKindedBox(5)

    <console>:14: error: ...
    argument expression's type is not compatible with formal parameter type;
    scala> HigherKindedBox(Box2("hello", 6))

    ???
    scala> HigherKindedBox(Box2("hello", 6))

    <console>:16: error: ...
    argument expression's type is not compatible with formal parameter type;

    T      = Box2[String, *]
    T[Int] = Box2[String, Int]

    ???

    Частичная унификация

    Флаг компилятора:  -Ypartial-unification

    https://github.com/typelevel/kind-projector

    box: HigherKindedBox[Box2[String, *]]=
      HigherKindedBox(Box2("hello", 1))