Parametric polymorphism in scala:
Generics and HKT
Parametric polymorphism in scala:
Generics and HKT
Plan
- Simple slides
- Not so simple slides
- Exercises
- Uncomfortably not simple slides
During slides write your guess to ms teams chat (or shout)
val numbers: String = "1 2 3 4 5"
def foo(x: Data): Oolong = None
case class Data(name: String, age: Int)
Recap: Types
case class Data(...)
sealed trait HardToUseData
List[String]
type Oolong = Option[Long]
Recap: Types
val a = 1
def foo(b: Int) = {
val c = a + b
}
Functions
case class A()
def foo[B](f: A => B) = {
val x: B = f(A())
}
Generic Functions
case class Pair[A, B](first: A, second: B)
def toList[A](pair: Pair[A, A]): List[A] = {
val x: A = pair.first
val y: A = pair.second
List(x, y)
}
Generics Types
List[+A]
Vector[+A]
Set[A]
Map[K, +V]
Generic collections
case class Triple[A](first: A, second: A, third: A){
def map[B](f: A => B): Triple[B] = {
val x: B = f(first: A)
val y: B = f(second: A)
val z: B = f(third: A)
Triple(x, y, z)
}
}
Generic scopes
Parametric Polymorphism
- 2-nd order logic / System F, J.-Y. Girard(1972), J. C. Reynolds(1974)
-
ML, R. Milner, since 1975 (programming language with PP and type inference)
-
Haskell, since 1990, S. Peyton-Jones, P. Wadler etc. (2-nd order PP, HKT, Type-classes)
-
System F<:, 1990-s, L. Cardelli., S.Martini, J.C.Mitchell,
-
Generic Java 1999, M.Odersky et al (java extension with PP, type bounds)
-
Scala, since 2004, M.Odersky (type bounds, variance, HKT, implicits)
Generics
Universal representation
(everything is a reference )
- Ocaml
- Haskell (?)
- Java
- Typescript
-
Scala
Generic structures
(value types)
- C#
- Rust
- Swift
- Go
Type Erasure
Type Parameters Relevance
Irrelevant
- Ocaml
- Haskell (?)
- Java
- Typescript
-
Scala
- Rust (?)
- Go
- Swift
Relevant
- C#
- C++
Type Parameters
def foo[A, B](x: List[A]): Option[B]
~
foo: (A: Type, B: Type) => (x: List[A]) => Option[B]
class Foo[A, B](x: List[A], y: Option[B])
Foo : (A: Type, B: Type) => Type
Subtyping
abstract class Animal
class Cat extends Animal
class Dog extends Animal
[Cat <: Animal]
[Dog <: Animal]
[Cat ? Dog]
Barbara Liskov Substitution Principle:
Let ϕ ( x ) be a property provable about objects x of type T.
Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.
Everything that is known about elements of supertype should apply to elements of subtype
Apply Carefully !!!!!!!!!!!!
case class Triple[A <: Animal](first: A, second: A, third: A)
Triple(1, 2, 3) // Compile Error!!!
Triple(cat, dog, cat) // Ok
Generic Upper Bounds
case class Triple[A](first: A, second: A, third: A){
def withFirst[A1 >: A](x: A1): Triple[A1] =
Triple(x, second, third)
}
Generic Lower Bounds
case class Triple[A](...)
~
case class Triple[A >: Nothing <: Any]
Double Bounds
def foo[A >: Dog <: Animal] // implies Dog <: Animal
def foo[A >: Dog <: Cat] // TYPE ERROR !!!!
Subtyping
abstract class Animal
class Cat extends Animal
class Dog extends Animal
[Cat <: Animal]
[Dog <: Animal]
[Cat ? Dog]
Covariant
F[+_]
A <: B
F[A] <: F[B]
sealed trait List[+A]
sealed trait Vector[+A]
sealed trait Map[K, +V]
type Function1[-A, +B]
sealed trait IO[+A]
sealed trait ZIO[-R, +E, +A]
Covariant
F[+_]
def dog: Dog
def cat: Cat
val dogs = List(dog)
val cats = List(cat)
val animals = List(cat, dog)
val nothing = List()
val anything = List(1, "1")
dogs : List[Animal]
cats : List[Animal]
animals: List[Animal]
nothing: List[Animal]
anything: List[Animal]
dogs: List[Dog]
cats: List[Dog]
animals: List[Dog]
nothing: List[Dog]
anything: List[Dog]
dogs: List[Cat]
cats: List[Cat]
animals: List[Cat]
nothing: List[Cat]
anything: List[Cat]
Contravariant
F[-_]
A <: B
F[A] >: F[B]
trait Consumer[-A]
type Function2[-A, -B, +R]
sealed trait ZIO[-R, +E, +A]
Contravariant
F[-_]
trait Says[-A]{
def say(a: A): String
}
// Says[-A] = A => String
val dogSays: Says[Dog] =
_ => "Woof"
val catSays: Says[Cat] =
_ => "Meow"
val animalSays: Says[Animal] = {
case _ : Cat => "I'm a cat"
case _ : Dog => "I'm a dog"
}
val anyoneSays: Says[Any] =
_ => "I am"
val nooneSays: Says[Any] =
_ => "Scala is so easy to learn"
val catSays: Says[Animal]
val dogSays: Says[Animal]
val animalSays: Says[Animal]
val anyoneSays: Says[Animal]
val nooneSays: Says[Animal]
val catSays: Says[Dog]
val dogSays: Says[Dog]
val animalSays: Says[Dog]
val anyoneSays: Says[Dog]
val nooneSays: Says[Dog]
val catSays: Says[Cat]
val dogSays: Says[Cat]
val animalSays: Says[Cat]
val anyoneSays: Says[Cat]
val nooneSays: Says[Cat]
Contravariant
F[-_]
trait Says[-A]{
def say(a: A): String
}
// Says[-A] = A => String
val dogSays: Says[Dog] =
_ => "Woof"
val catSays: Says[Cat] =
_ => "Meow"
val animalSays: Says[Animal] = {
case _ : Cat => "I'm a cat"
case _ : Dog => "I'm a dog"
}
val anyoneSays: Says[Any] =
_ => "I am"
val nooneSays: Says[Nothing] =
_ => "Scala is so easy to learn"
val catSays: Says[Animal]
val dogSays: Says[Animal]
val animalSays: Says[Animal]
val anyoneSays: Says[Animal]
val nooneSays: Says[Animal]
val catSays: Says[Dog]
val dogSays: Says[Dog]
val animalSays: Says[Dog]
val anyoneSays: Says[Dog]
val nooneSays: Says[Dog]
val catSays: Says[Cat]
val dogSays: Says[Cat]
val animalSays: Says[Cat]
val anyoneSays: Says[Cat]
val nooneSays: Says[Cat]
Composition
F[+G[+_]]
type Matrix[+A] = Vector[Vector[A]]
type Results[+A] = IO[List[A]]
Composition
F[+G[-_]]
type Consumers[-A] = List[Consumer[A]]
type ShowWithPrefix[-A] =
String => (A => String)
Composition
F[-G[-_]]
type Task[+A] = (A => Unit) => Unit
HKT
class Couple[A](x: A, y: A)
Couple : (A: Type) => Type
Higher Kinded Type parameters
case class ListAndOption[A, B](x: List[A], y: Option[B])
ListAndOption : (A: Type, B: Type) => Type
case class FPair[F[_], G[_], A, B](x: F[A], G[A])
type ListAndOption[A, B] = FPair[List, Option, A, B]
Higher Kinded Type parameters
case class Compose[F[+_], G[+_], +A](value: F[G[A]])
type IntFunction[+A] = Int => A
type Matrix[+A] = Compose[Vector, Vector, A] // value: Vector[Vector[A]]
type BinaryIntFunction =
Compose[IntFunction, IntFunction, Int] // value: Int => Int => Int
Kind
Animal // // *
Set[A] // [_] // * -> *
List[+A] // [+_] // + -> *
Pair[A, B] // [_, _] // * -> * -> *
Function[-A, +B] // [-_, +_] // - -> + -> *
Fix[F[_]] // [_[_]] // (* -> *) -> *
Free[F[_], A] // [_[_], _] // (* -> *) -> * -> *
Iteratee[F[+_], -A, +R] // [_[+_], -_, +_] // (+ -> *) -> - -> + -> *
Zoo[+X <: Animal] // [+_ <: Animal]
Food[-X >: Animal] // [-_ >: Animal]
Kind = Type of Types
Subtyping and Bounds
F[_] <: G[_] iff for all A : F[A] <: G[A]
def flatten[F[x] <: Iterable[x], A](fx: F[F[A]]): F[A] = ???
class Collection[Get[x] >: (Int => x), A](get: Get[A])
Subkinding
[+_] <: [_]
[-_] <: [_]
[F[+_]] >: [F[_]]
[F[-_]] >: [F[_]]
[_] <: [_ >: A <: B] // A <: B
//when A1 >: A and B1 <: B
[_ >: A1 <: B1] <: [_ >: A <: B]
Kind = Type of Types
Type Lambdas
sealed trait Free[+F[+_], +A]
type IntPair[+A] = (Int, A)
type IntStream[B] = Free[IntPair, B]
type Stream[A, B] = Free[???, B]
type Stream[A, B] = Free[(A, *), B]
Type Lambdas
type Stream[A, B] = Free[(A, *), B]
type FancyParser[A, B] = Nested[A => *, Either[String, *], B]
type FanciestParser[-A, +B] = Compose[A => +*, Either[String, +*], B]
type Pair[+A] = (A, A)
type BinaryTree[A] = Free[Lambda[x => (x, x)], A]
type Four[A] = Compose[Lambda[`+x` => (x, x)], Lambda[`+x` => (x, x)], A]
Either[String, *] ~ ({type L[A] = Either[String, A]})#L
Scala 3 Type Lambdas
type Stream[A, B] = Free[[x] =>> (A, x), B]
type FancyParser[A, B] =
Nested[[x] =>> A => x, [x] =>> Either[String, x], B]
type FanciestParser[-A, +B] =
Compose[[x] =>> A => x, [x] =>> Either[String, x], B]
type Pair[+A] = (A, A)
type BinaryTree[A] = Free[[x] =>> (x, x), A]
type Four[A] = Compose[[x] =>> (x, x), [x] =>> (x, x), A]
SUB or SUPER
HKT
By Oleg Nizhnik
HKT
- 731