# "There is nothing more practical than a good theory"

- Kurt Lewin

It's all about how expressive type system we want/need/can live with - where is the point of impracticality?

# "There is nothing more practical than a good theory"

- Kurt Lewin

It's all about how expressive type system we want/need/can live with - where is the point of impracticality?

## Two theoretical tools for exploring type systems

• λ-cube - visual
• PTS (Pure Type Systems) framework - definitional

## λ-cube

Visualizes three "dimensions" of type-system abstraction. What is this abstraction? Informally, it's mutual dependency of types and terms:

• term - term (typed lambda calculi)
• terms - type (polymorphism)
• type - term (dependent types)
• type - type (higher-order types)

## Relating abstractions

### Term - term

val x = 2
val y = 2
val z: Int = x * y

//or

def multiply(x: Int, y: Int): Int = x * y
val double: Int => Int = multiply(_, 2)
twice:  (Int -> Int) -> Int -> Int
twice   f               x   =  f (f x)

quadruple = twice (\x => x * 2)


## Relating abstractions

### Term - type

def identity[A](x: A) = x

def twice[A](a: A)(f: A => A): A = f(f(a))
twice:  (a -> a)        -> a -> a
twice   f               x    =  f (f x)

quadruple = twice (\x => x * 2)


## Relating abstractions

### Type - type

trait Function1[-T1, +R] {
def apply(v1: T1): R
}

trait Invariant[F[_]] {
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]
}

data List : Type -> Type where
Nil     : List elem
(::)    : elem -> List elem -> List elem

interface Functor (f : Type -> Type) where
map : (func : a -> b) -> f a -> f b


## Relating abstractions

### Type - term

trait Dep {
type V
val value: V
}

def dep(that: Dep): that.V = that.value

// dep: (that: Dep)that.V 
data Vect : Nat -> Type -> Type where
Nil  : Vect Z a
(::) : a -> Vect k a -> Vect (S k)


## PTS

Alternatively, using just six typing rules (four general rules valid for all systems + two specific rules that differentiate between them) one can derive a large family of type-systems.

All "corners" of λ-cube can be represented as instances of PTS (as well as many others like System U where all types are inhabited).

## PTS

single syntactic category for regular terms and types

$$\Pi x:A.B$$ family of types which assigns to each term x of sort A  a type B ( x )

## $$\Pi-type$$

pi: (x: t) -> (B: t -> Type) -> Type
pi x B = B(x)

funType: (A:Type) -> (B: Type) -> Type =
pi A (const B)
polyType: (A: Type) -> (constr: Type -> Type) -> Type =
pi A constr
polyTypeKind3: (A: Type) -> (B: Type) -> (constr: Type -> Type -> Type) -> Type =
pi A (\a => pi B (constr a))

depVectorOfReals: (n: Nat) -> Type =
pi n (\n => Vect n Double)

{-
Idris> polyType String List
polyType String List : Type = List String
Idris> polyTypeKind3 String Nat Either
polyTypeKind3 String Nat Either : Type = Either String Nat
Idris> depVectorOfReals 10
depVectorOfReals 10 : Type = Vect 10 Double
-}

## PTS

$$\Box$$ describes type kinds ie. types of type constructors, like: $$\star \rightarrow \star \rightarrow \star$$

## Sorts - types and kinds

In our type systems we may want to introduce following abstractions:

• $$(\lambda m:A.Fm): (A \to B)$$ where $$F:A \to B$$
• $$(\lambda \alpha : \star . \lambda a:\alpha.a): (\Pi \alpha : \star . (\alpha \to \alpha))$$ (polymorphic identity function)
• $$(\lambda \alpha : \star . \alpha \to \alpha):(\star \to \star)$$ (polymorphic Id type)

First two are clearly types. But what is $$\star \to \star$$? Probably not a type just as List or Map is not a type. Hence, sort of kinds

## PTS

Type formation rule restricts which sorts we are allowed to quantify over. In turn, this restricts which abstractions can be introduced by the abstraction rule

## $$\lambda ^ \to$$

Only term-term dependencies are allowed. The only type constructor is function type

## $$\lambda ^ \to$$ examples

The following can be derived

• $$A:\star , B:\star, b:B \vdash (\lambda a:A.b):B$$
• $$A:\star , B:\star, b:B, c:A \vdash ((\lambda a:A.b)c):B$$
• $$A:\star, B:\star \vdash (\lambda a:A.\lambda b:B.a):((A \to B) \to A)$$

## $$\lambda ^ \to$$ examples

def length(s: String) = ???
def isValid(s: String): Boolean = length(s) < 5

//no polymorphism
def intIdentity(i: Int) = i
def stringIdentity(s: String) = s

def andThen(f: Int => String, g: String => Boolean) = (x: Int) => g(f(x))

We can compute quite a lot, but this is not exactly practical for development because:

THERE ARE NO "REAL" TYPES

THERE IS NO POLYMORPHISM

## $$\lambda \underline{\omega}$$

Let's add type operators to simply-typed lambda calculi. We do this by allowing another pair of sorts to appear in typing rules: $$(\Box, \Box)$$ . In addition to previous rules, we now can type:

## $$\lambda \underline{\omega}$$ examples

The following can be derived

• $$\vdash (\lambda \alpha :\star.\alpha ):( \star \to \star ):\Box$$
• $$\alpha:\star , f:\star \to \star \vdash f\alpha:\star$$
• $$\alpha:\star \vdash (\lambda f:\star \to \star.f\alpha):((\star \to \star) \to \star)$$

## $$\lambda \underline{\omega}$$ examples

//this we can derive
trait Either[A, B]
type Id[A] = A
trait List[A]

//but, we cannot use it generically because
//term-level polymorphism is not derivable in lambda_omega, so

def length(l: List[Int]): Int = ???
// no def length[A](l: List[A]): Int = ???

def head(l: List[Int]): Either[String, Int] =
if (length(l) > 0) Right(l(0)) else Left("No such element")
// no def head[A](l: List[A]): Either[String, A]

We still DEMAND generic programming!

## System F

Polymorphism is added by allowing $$( \Box, \star )$$ combination (term-type dependency). With this rule, we obtain the famous System F (Hindley-Milner is a subset of System F)

## System F examples

The following can be derived

• $$\alpha : \star \vdash (\lambda a:\alpha .a ):( \alpha \to \alpha )$$
• $$\vdash (\lambda \alpha : \star.\lambda a:\alpha .a ): \Pi \alpha : \star . \alpha \to \alpha : \star$$
• $$A:\star , b:A \vdash (\lambda \alpha : \star.\lambda a:\alpha .a )Ab:A$$

## System Fω

Combining system F with type operators, we obtain system Fω, where we have what we're used to in modern functional programming.

Actually, we have much more because we can unleash full power of lambda calculus on type level.

## System Fω

System Fω allows universal quantification, abstraction and application at higher kinds.

Type systems of languages like Scala and Haskell deliver slightly less than Fω

## System Fω

For example in system F one can achieve higher-rank polymorphism, which is not possible (directly) in Scala (possible in Idris though)

def pair[A, B](f: A => (A, A),
b: B): (B, B) =
f(b)
/*
error: type mismatch;
found   : b.type
(with underlying type B)
required: A

We can't say, in fictional Scala
def pair[B](f: A => (A, A){forAll A},
b: B): (B, B) */
mkPair: ({a: _} -> a -> (a, a)) ->
b ->
(b, b)
mkPair  f b =  f(b)

{-
Idris> mkPair (\a => (a, a)) 10
(10, 10) : (Integer, Integer)
Idris> mkPair (\a => (a, a)) "Hello"
("Hello", "Hello") : (String, String)
-}

## System Fω - examples

The same proof translated to Scala/Idris could be:

trait &[A, B] {
def apply[γ](x: A => B => γ): γ
}
type AND[A, B] = A & B

def K1[A, B](x: A)(y: B) = x
def K2[A, B](x: A)(y: B) = y

// A AND B => A
def proof1[A, B](x: A AND B): A =
x[A](K1)
// A AND B => B
def proof2[A, B](x: A AND B): B =
x[B](K2)

data And : a -> b -> Type where
AND : (pi: {c: Type} ->
(a -> b -> c) -> c
) ->  And a b

prf1 : And a b -> a
prf1 (AND pi) = let
k = \x: a, y: b => x
in pi(k)

prf2 : And a b -> b
prf2 (AND pi) = let
k = \x: a, y: b => y
in pi(k)

## λ-cube

We haven't yet explored the possibility of allowing $$(\star , \Box)$$ rule. This enables type-term dependency.

Motivations (things we are not able to express):

• collections of known length
• type of dates where the range of the day is restricted according to the month
• functions like sprintf
• type-safe structural correctness (eg. compile-time balanced trees)

## Dependent typing

Question arises - whether additional type-safety is worth the effort of adapting it in languages that do not support it intrinsically?

Is it even possible?

- Miles Sabin

## Idris vs Scala

data PowerSource = Petrol | Pedal | Electric

data Vehicle : PowerSource -> Type where
Bicycle : Vehicle Pedal
Motorcycle : (fuel: Nat) -> Vehicle Petrol
Car : (fuel: Nat) -> Vehicle Petrol
Bus : (fuel: Nat) -> Vehicle Petrol
Tram : Vehicle Electric

wheels : Vehicle power -> Nat
wheels Bicycle = 2
wheels (Motorcycle _) = 2
wheels (Car _) = 4
wheels (Bus _) = 4
wheels Tram = 8

refuel : Vehicle Petrol -> Vehicle Petrol
refuel (Car _) = Car 100
refuel (Bus _) = Bus 200
refuel (Motorcycle _) = Motorcycle 50
sealed trait Vehicle {
def wheels: Int
}
sealed trait PedalVehicle extends Vehicle
sealed trait ElectricVehicle extends Vehicle
sealed trait PetrolVehicle extends Vehicle {
def fuel: Int
def refuel(): PetrolVehicle
}

case object Bicycle
extends PedalVehicle {val wheels = 2}
case object Tram
extends ElectricVehicle {val wheels = 8}

case class Motorcycle(fuel: Int)
extends PetrolVehicle {
val wheels = 2
def refuel() = Motorcycle(50)
}

//...

enumerated dep.

encoded as subtypes

## Idris vs Scala

data PowerSource = Petrol | Pedal | Electric

data Vehicle : PowerSource -> Type where
Bicycle : Vehicle Pedal
Motorcycle : (fuel: Nat) -> Vehicle Petrol
Car : (fuel: Nat) -> Vehicle Petrol
Bus : (fuel: Nat) -> Vehicle Petrol
Tram : Vehicle Electric

wheels : Vehicle power -> Nat
wheels Bicycle = 2
wheels (Motorcycle _) = 2
wheels (Car _) = 4
wheels (Bus _) = 4
wheels Tram = 8

refuel : Vehicle Petrol -> Vehicle Petrol
refuel (Car _) = Car 100
refuel (Bus _) = Bus 200
refuel (Motorcycle _) = Motorcycle 50
sealed trait Vehicle {
def wheels: Int
}
sealed trait PowerSource { self: Vehicle => }
trait Pedal
extends PowerSource { self: Vehicle => }
trait Electric
extends PowerSource { self: Vehicle => }
trait Petrol { self: Vehicle =>
def fuel: Int
def refuel(): Vehicle with Petrol
}
case object Bicycle
extends Vehicle with Pedal
{val wheels = 2}
case object Tram
extends Vehicle with Electric
{val wheels = 8}
case class Car(fuel: Int)
extends Vehicle with Petrol {
val wheels = 4
def refuel() = Car(100)
}


encoded as mixin

## Idris vs Scala

data PowerSource = Petrol | Pedal | Electric

data Vehicle : PowerSource -> Type where
Bicycle : Vehicle Pedal
Motorcycle : (fuel: Nat) -> Vehicle Petrol
Car : (fuel: Nat) -> Vehicle Petrol
Bus : (fuel: Nat) -> Vehicle Petrol
Tram : Vehicle Electric

wheels : Vehicle power -> Nat
wheels Bicycle = 2
wheels (Motorcycle _) = 2
wheels (Car _) = 4
wheels (Bus _) = 4
wheels Tram = 8

refuel : Vehicle Petrol -> Vehicle Petrol
refuel (Car _) = Car 100
refuel (Bus _) = Bus 200
refuel (Motorcycle _) = Motorcycle 50
sealed trait Vehicle[PS <: PowerSource] {
def wheels: Int
}
sealed trait PowerSource
trait Pedal extends PowerSource
trait Electric extends PowerSource
trait Petrol extends PowerSource

//...

case class Bus(fuel: Int)
extends Vehicle[Petrol] {
val wheels = 4
def refuel() = Bus(200)
}
def refuel(vehicle: Vehicle[Petrol])
:Vehicle[Petrol] = vehicle match {
case c@Car(_) => c.refuel()
case b@Bus(_) => b.refuel()
case m@Motorcycle(_) => m.refuel()
}
}



phantom type

## Idris vs Scala

data Nat = Z | S Nat

-- S ( S (Z) )
--  2 : Nat
-- n: Nat = 500
--  n : Nat = 500

head : Vect (S len) elem -> elem
head (x::xs) = x
sealed trait Nat {
type N <: Nat
}

case object Z
extends Nat {type N = Z.type}
case class S[P <: Nat]()
extends Nat {type N = S[P]}

object Nat {
type _0 = Z.type
type _1 = S[_0]
type _2 = S[_1]
type _3 = S[_2]

val _0: _0 = Z
val _1: _1 = S[_0]
val _2: _2 = S[S[_0]]
val _3: _3 = S[S[S[_0]]]
}



recursive type

type member carrying "result"

## Idris vs Scala

data Nat = Z | S Nat

-- S ( S (Z) )
--  2 : Nat
-- n: Nat = 500
--  n : Nat = 500

readNat = do input <- getLine
pure (parsePositive input)
sealed trait Nat {
type N <: Nat
}

case object Z
extends Nat {type N = Z.type}
case class S[P <: Nat]()
extends Nat {type N = S[P]}

object Nat {
//...

implicit def apply(i: Int): Nat = macro ...
def toInt[N <: Nat] = macro ...
def toInt(n: Nat) = macro ...
}



automatic rep of literals

macros / literals only!

## Idris vs Scala

data Nat = Z | S Nat

-- S ( S (Z) )
--  2 : Nat
-- n: Nat = 500
--  n : Nat = 500

readNat = do input <- getLine
pure (parsePositive input)
sealed trait Nat {
type N <: Nat
}

case object Z
extends Nat {type N = Z.type}
case class S[P <: Nat]()
extends Nat {type N = S[P]}

object Nat {
//...
transparent def toNat(n: Int): Nat =
n match {
case 0 => Z
case n if n > 0 => S(toNat(n - 1))
}
transparent def toInt[N <: Nat]: Int =
anyValue[N] match {
case _: Z => 0
case _: S[n] => toInt[n] + 1
}
}

automatic rep of literals

in Dotty it might be easier due to the so-called transparent functions / literals only!

## Idris vs Scala

data Nat = Z | S Nat

-- S ( S (Z) )
--  2 : Nat
-- n: Nat = 500
--  n : Nat = 500

readNat = do input <- getLine
pure (parsePositive input)

tail : Vect (S len) elem -> Vect len elem
tail (x::xs) = xs
sealed trait Nat {
type N <: Nat
}

case object Z
extends Nat {type N = Z.type}
case class S[P <: Nat]()
extends Nat {type N = S[P]}

object Nat {
//...
def apply(i: Int): Option[Nat] = {
@scala.annotation.tailrec
def succ(j: Int, n: Nat = _0): Nat = {
val s = S[n.N]
if (j == 0) s else succ(j - 1, s)
}

if (i < 0) None
else if (i == 0) Some(_0)
else Some(succ(i - 1))
}
}

alternatively - but: no type preservation and we can't pattern match on type level anyway

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

-- natToFin 2 3
--  Just (FS (FS FZ)) : Maybe (Fin 3)
-- natToFin 0 3
--  Just FZ : Maybe (Fin 3)
sealed trait Fin[SK <: S[_]]

case class FZ[SK <: S[_]]()
extends Fin[SK]
case class FS[K <: S[_], F <: Fin[K]]()
extends Fin[S[K]]



type indexed by integer

type-level recursion

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

trait FinToNat[F <: Fin[_]] {

type Out <: Nat

def apply(): Out

}

object FinToNat {
type Result[F <: Fin[_], N <: Nat] =
FinToNat[F] {type Out = N}

// ...
}



encode the "result" type, known as Aux pattern

lift computation to a type

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

object FinToNat {
type Result[F <: Fin[_], N <: Nat] =
FinToNat[F] {type Out = N}

implicit def caseFZ[N <: S[_]]:
Result[FZ[N], _0] = new FinToNat[FZ[N]] {
type Out = _0
def apply() = _0
}
implicit def caseFS[K <: S[_],
F <: Fin[K],
SoFar <: Nat](
implicit finToNatK: Result[F, SoFar]):
Result[FS[K, F], S[SoFar]] =
new FinToNat[FS[K, F]] {
type Out = S[SoFar]
def apply() = S[SoFar]()
}

def apply[F <: Fin[_]](
implicit finToNat: FinToNat[F]):
Result[F, finToNat.Out] = finToNat
}

encode computation as implicit search

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

sealed trait Fin[SK <: S[_]]

case class FZ[SK <: S[_]]()
extends Fin[SK]
case class FS[K <: S[_], F <: Fin[K]]()
extends Fin[S[K]]

object Fin {
def finToNat[F <: Fin[_]](f: F)(
implicit finToNat: FinToNat[F]):
finToNat.Out = finToNat()
}

/*
scala> FS[_4, FZ[_4]]
res1: FS[Nat._4,FZ[Nat._4]] = FS()

scala> Fin.finToNat(res1)
res2: S[Z.type] = S()
*/

encode computation as implicit search

preserve structure representation

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

trait NatToFin[M <: Nat, N <: S[_]] {
type Out <: Fin[N]
def apply(): Out
}

object NatToFin {
type Result[M <: Nat,
N <: S[_],
F <: Fin[N]] =
NatToFin[M, N] {
type Out = F
}
// ...
}



encode the "result" type, known as Aux pattern

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

object NatToFin {
//...

implicit def caseZSj[J <: S[_]]:
Result[_0, J, FZ[J]] =
new NatToFin[_0, J] {
type Out = FZ[J]
def apply(): Out = FZ[J]()
}

implicit def caseSkSj[K <: Nat, J <: S[_]](
implicit natToFinKJ: NatToFin[K, J]):
Result[S[K], S[J], FS[J, natToFinKJ.Out]] =
new NatToFin[S[K], S[J]] {
type Out = FS[J, natToFinKJ.Out]
def apply(): Out = FS[J, natToFinKJ.Out]
}

def apply[M <: Nat, N <: S[_]](
implicit natToFin: NatToFin[M, N]):
Result[M, N, natToFin.Out] = natToFin
}

encode computation as implicit search

## Idris vs Scala

data Fin : (n : Nat) -> Type where
FZ : Fin (S k)
FS : Fin k -> Fin (S k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS k) = S (finToNat k)

natToFin : Nat -> (n : Nat) -> Maybe (Fin n)
natToFin Z     (S j) = Just FZ
natToFin (S k) (S j) with (natToFin k j)
| Just k' = Just (FS k')
| Nothing = Nothing
natToFin _ _ = Nothing

object Fin {
//...

def natToFin[M <: Nat, N <: S[_]](
implicit natToFin: NatToFin[M, N]):
natToFin.Out = natToFin()
def natToFin[M <: Nat, N <: S[_]](m: M,
n: N)(
implicit natToFin: NatToFin[m.N, n.N]):
natToFin.Out = natToFin()
}
/*
scala> Fin.natToFin(_2, _3)
res1: FS[S[S[Z.type]],...] = FS()

scala> Fin.natToFin(_6, _2)
error: could not find implicit value
for parameter natToFin:
NatToFin[Nat._6.N,Nat._2.N]
Fin.natToFin(_6, _2)
*/

## Idris vs Scala

data Vect : (len : Nat) ->
(elem : Type) ->
Type where
Nil  : Vect Z elem
(::) : (x : elem) -> (xs : Vect len elem) ->
Vect (S len) elem

tail : Vect (S len) elem -> Vect len elem
tail (x::xs) = xs

head : Vect (S len) elem -> elem

sealed trait Vect[Len <: Nat, +Elem] {
type Repr <: Vect[Len, Elem]
}
case object NIL extends Vect[_0, Nothing] {
type Repr = NIL.type

def ::[Elem](elem: Elem):
Cons[_0, Elem, this.type] =
Cons(elem, this)
}
case class Cons[Len <: Nat,
Elem,
XS <: Vect[Len, Elem]](
extends Vect[S[Len], Elem] {
type Repr = Cons[Len, Elem, XS]

Cons[S[Len], Elem0, this.type] =
}

## Idris vs Scala

data Vect : (len : Nat) ->
(elem : Type) ->
Type where
Nil  : Vect Z elem
(::) : (x : elem) -> (xs : Vect len elem) ->
Vect (S len) elem

tail : Vect (S len) elem -> Vect len elem
tail (x::xs) = xs

head : Vect (S len) elem -> elem

sealed trait Vect[Len <: Nat, +Elem] {
type Repr <: Vect[Len, Elem]

def head(implicit ...): Elem = ???
//this match { case Cons(x, xs) =>
def tail(implicit ...): ??? = ???
}



pattern match on type level - only non-empty vectors

doesn't compile - you can't pattern match on generic data types

these have to come from the implicit evidence - (S len) is not enough

## Idris vs Scala

data Vect : (len : Nat) ->
(elem : Type) ->
Type where
Nil  : Vect Z elem
(::) : (x : elem) -> (xs : Vect len elem) ->
Vect (S len) elem

tail : Vect (S len) elem -> Vect len elem
tail (x::xs) = xs

head : Vect (S len) elem -> elem

trait IsCons[V <: Vect[Len, Elem],
Len <: Nat,
Elem] {
type XS <: Vect[_, Elem]

def x(vect: V): Elem
def xs(vect: V): XS
}

object IsCons {
type Result[V <: Vect[S[Len], Elem],
Len <: Nat,
Elem,
XS0 <: Vect[Len, Elem]] =
IsCons[V, S[Len], Elem] { type XS = XS0 }

}



missing type-level pattern match

capture type of tail

"unapply"

constraints

## Idris vs Scala

data Vect : (len : Nat) ->
(elem : Type) ->
Type where
Nil  : Vect Z elem
(::) : (x : elem) -> (xs : Vect len elem) ->
Vect (S len) elem

tail : Vect (S len) elem -> Vect len elem
tail (x::xs) = xs

head : Vect (S len) elem -> elem

sealed trait Vect[Len <: Nat, +Elem] {
type Repr <: Vect[Len, Elem]
protected def _this: Repr

implicit ev: IsCons[Repr, Len, Elem0]):
Elem0 =
ev.x(_this)
def tail[Elem0 >: Elem](
implicit ev: IsCons[Repr, Len, Elem0]):
ev.XS =
ev.xs(_this)
}



these come from the implicit evidence - (S len) is not enough

## Idris vs Scala

*> :let v = 1 :: 2 :: Nil
*> :t v
v : Vect 2 Integer

*> head . tail $v 2 : Integer *> tail . tail$ v
[] : Vect 0 Integer
*> tail . tail . tail $v Type mismatch between Vect 2 Integer (Type of v) and Vect (S (S (S len))) elem (Expected type) Specifically: Type mismatch between 0 and S len  scala> val v = 1 :: 2 :: NIL v: Cons[S[Z.type], Int, _ <: Cons[Nat._0,Int,NIL.type]] = Cons(1,Cons(2,NIL)) scala> v.tail.head res2: Int = 2 scala> v.tail.tail res3: NIL.type = NIL scala> v.tail.tail.head <console>:13: error: could not find implicit value for parameter ev: IsCons[v.tail.tail.Repr,Nat._0,Elem0] v.tail.tail.head ^ scala> v.tail.tail.tail <console>:13: error: could not find implicit value for parameter ev: IsCons[v.tail.tail.Repr,Nat._0,Elem0] v.tail.tail.tail ^  ## Questions? Hey, but this is cheating! All these examples assume the size of collection to be statically known. My collections come from the Internet (database/file)! ## Σ type This seems somewhat problematic: how to represent vectors of unknown length if length is part of the type? In dependent typing theory there is a device for this called Σ type (a.k.a. dependent pair) data DPair : (a : Type) -> (a -> Type) -> Type where MkDPair : (x : a) -> P x -> DPair a P -- syntactic sugar for this is ** -- for example filter : (elem -> Bool) -> Vect len elem -> (p : Nat ** Vect p elem)  ## Σ type vectFromTheInternetOrDatabaseOrFile : IO (len ** Vect len String) doSthWithSuchVector : (length ** Vect length String) -> Maybe String doSth (Z ** _) = Nothing doSth (S (S (S Z)) ** vec3) = Just$ head . tail $vec3 doSth (S k ** nonEmpty) = Just$ head nonEmpty
-- but
doSth (S k ** nonEmpty) = Just $head . tail$ nonEmpty
{-
Type mismatch between
(\length => Vect length String) (S k) (Type of nonEmpty)
and
Vect (S (S len)) String (Expected type)

Specifically:
Type mismatch between
k
and
S len
-}

## Σ type

Σ type can be expressed in Scala (awkwardly though)

trait DType[A] {
type T
}
object DType {
type **[A, T0] = DType[A] { type T = T0 }
def apply[A, T0](): A ** T0 = new DType[A] { type T = T0 }
}

abstract class DPair[A, B](implicit P: A ** B) {
def x: A
def y: B
}

## Σ type - Scala examples

sealed trait State
case class LoggedIn(user: User) extends State
case object LoggedOut extends State

class LoggedInActions(user: User) {
def sayHello() = s"Hello ${user.name}!" } class LoggedOutActions { def logIn(password: String): User = ??? } implicit val loggedIn: LoggedIn ** LoggedInActions = DType() implicit val loggedOut: LoggedOut.type ** LoggedOutActions = DType() val dPairLoggedIn = DPair(LoggedIn(User("Marcin")))( x => new LoggedInActions(x.user)) val dPairLoggedOut = DPair(LoggedOut, new LoggedOutActions()) val noCheating = DPair(LoggedOut, new LoggedInActions(User("Marcin"))) // Error: could not find implicit value for // parameter P: LoggedOut.type ** LoggedInActions ## Σ type - Scala examples import Nat._ implicit def mkDepVect[N <: Nat, A]: N ** Vect[N, A] = DType() val anyVec = DPair(_2, (1 :: 2 :: NIL)) //OK val noCheating = DPair(_3, (1 :: 2 :: NIL)) //does not compile //Error:(53, 24) could not find implicit value for //parameter P: Nat._3 ** Vect[Nat._2,Int]  So theoretically we could represent vectors of length dependent on a "variable" in Scala. Unfortunately, ... ## If you recall this slide ... data Nat = Z | S Nat -- S ( S (Z) ) -- 2 : Nat -- n: Nat = 500 -- n : Nat = 500 readNat: IO (Maybe Nat) readNat = do input <- getLine pure (parsePositive input) tail : Vect (S len) elem -> Vect len elem tail (x::xs) = xs sealed trait Nat { type N <: Nat } case object Z extends Nat {type N = Z.type} case class S[P <: Nat]() extends Nat {type N = S[P]} object Nat { //... def apply(i: Int): Option[Nat] = { @scala.annotation.tailrec def succ(j: Int, n: Nat = _0): Nat = { val s = S[n.N] if (j == 0) s else succ(j - 1, s) } if (i < 0) None else if (i == 0) Some(_0) else Some(succ(i - 1)) } } alternatively - but: no type preservation and we can't pattern match on type level anyway ## ... this means that scala> u.weakTypeOf[_2.N] res1: reflect.runtime.universe.Type = Nat._2.N scala> res1.dealias res2: reflect.runtime.universe.Type = S[Nat._1] scala> implicitly[IsSucc[_2.N]] res16: IsSucc[Nat._2.N] = IsSucc$$anon$1@24a6b595

scala> val n = Nat(2).get
n: Nat = S()
scala> u.weakTypeOf[n.N]
res3: reflect.runtime.universe.Type = n.N
scala> res3.dealias
res4: reflect.runtime.universe.Type = n.N
scala> val m: Nat = _2
m: Nat = S()
scala> u.weakTypeOf[m.N]
res5: reflect.runtime.universe.Type = m.N
scala> res5.dealias
res6: reflect.runtime.universe.Type = m.N
scala> implicitly[IsSucc[m.N]]
error: could not find implicit value for parameter e: IsSucc[m.N]



this is the bridge you can't cross: if you ever loose your singleton type, compiler won't be able to keep track of all the structure - you won't recover it from a runtime value

## Scala vs Idris

In Scala you can't assert that a type structure is to be deduced from a runtime structure, while in Idris you can.

Even though you can express dependent vectors in Scala, it is not practical because you can only express them if their length is statically known (not really the case for vectors) and give up all the transformations that can't keep track of size (ie. filter)

## Scala vs Idris

data Format = Number Format
| Str Format
| Lit String Format
| End

PrintfType : Format -> Type
PrintfType (Number fmt)   = (i : Int) -> PrintfType fmt
{- ... -}
PrintfType (Lit str fmt)  = PrintfType fmt
PrintfType End            = String

printfFmt : (fmt : Format) -> (acc : String) -> PrintfType fmt
printfFmt (Number fmt) acc    = \i => printfFmt fmt (acc ++ show i)
{- ... -}
printfFmt End acc             = acc

toFormat : (xs : List Char) -> Format
toFormat []                     = End
toFormat ('%' :: 'd' :: chars)  = Number (toFormat chars)
{- ... -}

printf : (fmt : String) -> PrintfType (toFormat (unpack fmt))
printf fmt = printfFmt _ ""
data Format = Number Format
| Str Format
| Lit String Format
| End

PrintfType : Format -> Type
PrintfType (Number fmt)   = (i : Int) -> PrintfType fmt
{- ... -}
PrintfType (Lit str fmt)  = PrintfType fmt
PrintfType End            = String

printfFmt : (fmt : Format) -> (acc : String) -> PrintfType fmt
printfFmt (Number fmt) acc    = \i => printfFmt fmt (acc ++ show i)
{- ... -}
printfFmt End acc             = acc

toFormat : (xs : List Char) -> Format
toFormat []                     = End
toFormat ('%' :: 'd' :: chars)  = Number (toFormat chars)
{- ... -}

printf : (fmt : String) -> PrintfType (toFormat (unpack fmt))
printf fmt = printfFmt _ ""

value is seamlessly lifted into type

*> :t printf "%s = %d"
printf "%s = %d" : String -> Int -> String
*> :t printf "%s = %f"
printf "%s = %f" : String -> Double -> String

## Scala vs Idris

data Format = Number Format
| Str Format
| Lit String Format
| End

PrintfType : Format -> Type
PrintfType (Number fmt)   = (i : Int) -> PrintfType fmt
{- ... -}
PrintfType (Lit str fmt)  = PrintfType fmt
PrintfType End            = String

printfFmt : (fmt : Format) -> (acc : String) -> PrintfType fmt
printfFmt (Number fmt) acc    = \i => printfFmt fmt (acc ++ show i)
{- ... -}
printfFmt End acc             = acc

toFormat : (xs : List Char) -> Format
toFormat []                     = End
toFormat ('%' :: 'd' :: chars)  = Number (toFormat chars)
{- ... -}

printf : (fmt : String) -> PrintfType (toFormat (unpack fmt))
printf fmt = printfFmt _ ""

bridge you can't cross (macro ?)

## Scala vs Idris

sealed trait Format
case class Num[F <: Format](rest: F)
extends Format
//...
case class Str[F <: Format](rest: F)
extends Format
case class Lit[F <: Format](string: String, rest: F)
extends Format
case object End extends Format

trait PrintfType[F <: Format] {
type Out
def apply(fmt: F): String => Out
}
object PrintfType {
type Result[F <: Format, Out0] = PrintfType[F] { type Out = Out0 }

implicit val endFmt: Result[End.type, String] = new PrintfType[End.type] {
type Out = String
def apply(fmt: End.type) = identity
}
// ...
}

## Scala vs Idris


//...
implicit def numberFmt[Fmt <: Format, Rest](
implicit printfFmt: Result[Fmt, Rest])
: Result[Num[Fmt], Int => Rest] =
new PrintfType[Num[Fmt]] {
type Out = Int => Rest
def apply(fmt: Num[Fmt]) =
(acc: String) => (i: Int) => printfFmt(fmt.rest)(acc + i)
}
//...
implicit def litFmt[Fmt <: Format, Rest](
implicit printfFmt: Result[Fmt, Rest]): Result[Lit[Fmt], Rest] =
new PrintfType[Lit[Fmt]] {
type Out = Rest
def apply(fmt: Lit[Fmt]) =
(acc: String) => printfFmt(fmt.rest)(acc + fmt.string)
}

def apply[F <: Format](format: F)(
implicit printfType: PrintfType[F]): printfType.Out =
printfType(format)("")
val fmt1 = Str(Lit(" = ", Num(End)))
val t1: String => Int => String = PrintfType(fmt1)
val fmt2 = Str(Lit(" = ", Dbl(End)))
val t2: String => Double => String = PrintfType(fmt2)
println (t2("answer")(10.0))

## Scala vs Idris

sprintf looks more practical because you can enjoy increased type-safety with no downside being limited to String literals (but you must employ a macro to parse format string)

## That's all Folks!

iterato.rs

medium.com/iterators​

slides from this presentation are available here: goo.gl/JsXGB6

Thanks  for coming

#### Idris for impractical Scala programmers

By Marcin Rzeźnicki

• 1,650