Parametric  polymorphism in scala:

Generics and HKT

 

Parametric polymorphism in scala:

Generics and HKT

 

Plan

  1. Simple slides
  2. Not so simple slides
  3. Exercises
  4. 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 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

  • 699