The Ascent To Idris
Zainab Ali
@_zainabali_
val people : Map[String, Person] = ...
val peopleAndMe : Map[String, Person] = people.put(
"Zainab" -> Person(...)
)
...
// but I know I exist
val me : Option[Person] = peopleAndMe.get("Zainab")// unsafe code
val me : Person = peopleAndMe("Zainab")The Valley of Agony
- All good programs should compile
- All bad programs shouldn't
- Sometimes a bad program will compile
- Sometimes a good program won't
More powerful types
Idris

data MyDataType : Type where
MyConstructor : (myLabel : String) -> MyDataType
myFunction : MyDataType -> String
myFunction (MyConstructor a) = a
myNumber : Nat
myNumber = 2data Vect : Nat -> Type -> Type where
Nil : Vect 0 a
(::) : a -> Vect n a -> Vect (n + 1) a
dogsILike : Vect 2 String
dogsILike = "Schnauzer" :: "Beagle" :: Nil
dogsIHate : Vect 0 String
dogsIHate = Nil
-- Type error!
cats : Vect 1 String
cats = NilTypes Dependent on Values
The Journey
- Begin with values
- Constraining types
- Types dependent on types
- Types dependent on values
The Problem : Accounts
- Valid operations are credit / debit / transfer
- Only integer amounts
- Accounts must be created with a positive balance
- Accounts can only be credited with a positive amount
- Accounts can only be debited with a positive amount
- Must record the number of operations
- Transfers must preserve the total amount
case class Account(balance: Int, opCount: Int)
def create(initialBalance: Int):
BalanceShouldBePositive Either Account = ...
def credit(amount: Int, account: Account):
AmountShouldBePositive Either Account = ...
def debit(amount: Int, account: Account):
AmountShouldBePositive Either Account = ...
def transfer(amount: Int,
from: Account,
to: Account):
AmountShouldBePositive Either (Account, Account) = ...In Scala
val right = create(100)
// has type AmountShouldBePositive Either Account
val left = create(0)
// has type AmountShouldBePositive Either Accountdata Account : Type where
MkAccount : (balance : Nat) -> (opCount : Nat) -> Account
create : Nat ->
Either BalanceShouldBePositive Account
create initialBalance = ...
credit : Nat ->
Account ->
Either AmountShouldBePositive Account
credit amount account = ...
debit : Nat ->
Account ->
Either AmountShouldBePositive Account
debit amount account = ...In Idris
transfer : Nat ->
Account ->
Account ->
Either AmountShouldBePositive (Account, Account)
transfer amount from to = ...Constraining Types
- Accounts must be created with a positive balance
- Accounts can only be credited with a positive amount
- Accounts can only be debited with a positive amount
create : (a: Nat) ->
(prf : a `GT` 0) ->
Account
create initialBalance _ = ...In Idris
-- Type error!
create 0 ?whatProof
amount : Nat
amount = ...
case (isGT 0 amount) of
Yes prf => create amount prf
No _ => ...Scala
libraryDependencies += "eu.timepit" %% "refined" % theVersionimport eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._def create(initialBalance: Int Refined Positive):
Account = ...// Yay!
val account: Account = create(100)// Fails!
val account: Account = create(-5)val amount : Int = ...
val refinedPositive : String Either (Int Refined Positive) =
refineV[Positive](amount)
refinedPositive.map { positiveAmount =>
create(positiveAmount)
}Types Dependent On Types
- Must record the number of operations
data Account : Type where
MkAccount : (balance : Nat) ->
(opCount : Nat) -> AccountIdris
data Account : (opCount : Nat) -> Type where
MkAccount : (balance : Nat) ->
(opCount : Nat) ->
Account opCountfiveOps : Account 5
fiveOps = MkAccount 10 5create : (a : Nat) ->
(prf : a `GT` 0) ->
Account 0
create initialBalance _ = ...In Idris
-- Type error!
create : (a : Nat) ->
(prf : a `GT` 0) ->
Account 0
create initialBalance _ = MkAccount initialBalance 3credit : (a : Nat) ->
Account n ->
(prf : a `GT` 0) ->
Account (n + 1)
credit amount account _ = ...In Idris
In Scala
- Literal types
- Implicits
- Singleton ops
5.typecase class Account[OpCount <: Int with Singleton](
balance : Int
)case class Account(balance : Int, opCount: Int)scalaOrganization := "org.typelevel"
scalacOptions += "-Yliteral-types"
Typelevel Scala
val fiveOps : Account[5] = Account[5](3)def create(amount: Int Refined Positive): Account[0] = ...def credit[N <: Int with Singleton](
amount: Int Refined Positive,
account: Account[N]
) : ??? = ...trait IncCount[A <: Account[_]] {
type Out <: Account[_]
def apply(account: A): Out
}
object IncCount {
type Aux[A <: Account[_], Out0 <: Account[_]] =
IncCount[A] {
type Out = Out0
}
}Typeclasses
implicit val incCount: IncCount.Aux[Account[0], Account[1]] =
new IncCount[Account[0]] {
type Out = Account[1]
def apply(a: Account[0]): Out = Account[1](a.balance)
}def credit[N <: Int with Singleton](
amount: Int Refined Positive,
account: Account[N])(
implicit incCount: IncCount[Account[N]]
): incCount.Out = ...libraryDependencies += "eu.timepit" %% "singleton-ops" % theVersionSingleton Ops
import singleton.ops._object IncCount {
implicit def incCount[N <: Int with Singleton,
IncN <: Int with Singleton](
implicit op: OpInt.Aux[N + 1, IncN]
): Aux[Account[N], Account[IncN]] =
new IncCount[A] {
type Out = Account[IncN]
def apply(account: A): Account[IntN] =
Account(account.balance)
}
}Types Dependent on Values
- Transfers must preserve the total amount
data Account : (opCount : Nat) -> Type where
MkAccount : (balance : Nat) ->
(opCount : Nat) ->
Account opCountIn Idris
data Account : (opCount : Nat) ->
(balance : Nat) -> Type where
MkAccount : (balance : Nat) ->
(opCount : Nat) ->
Account opCount balancecreate : (a : Nat) ->
(prf : a `GT` 0) ->
Account 0 a
create initialBalance _ = ...
credit : (a : Nat) ->
Account n balance ->
(prf : a `GT` 0) ->
Account (n + 1) (balance + a)
credit amount account _ = ...In Idris
-- Type error!
debit : (a : Nat) ->
Account n balance ->
(prf : a `GT` 0) ->
Account (n + 1) (balance - a)
debit amount account _ = ...In Idris
In Idris
debit : (a : Nat) ->
Account n balance ->
(prf : a `GT` 0) ->
(prf1 : a `LTE` balance) ->
Account (n + 1) (balance - a)
debit amount account _ _ = ...transfer : (a : Nat) ->
Account n nBalance ->
Account m mBalance ->
(prf : a `GT` 0) ->
(prf1 : a `LTE` nBalance) ->
(Account (n + 1) (balance - a),
Account (m + 1) (balance + a))
transfer amount from to _ _ = ...In Idris
In Scala?


Take a look at
Thank you!
Questions?
The Ascent to Idris
By Zainab Ali
The Ascent to Idris
Type level programming greatly increases the robustness of our code, but is difficult to express in Scala. Idris, a language where types are first class, is much better at expressing such concepts. But Scala has come a long way in the past few years. This talk takes a closer look at type level programming in Idris and Scala. We start with a basic problem defined using values, and slowly add types to it in both languages. By using singleton and refined types, as well as typelevel functions, we'll rise to the challenge of Idris
- 4,046