EvenOdd in Agda, Idris, Haskell, Scala
A while ago I blogged about using Agda to prove the parity of added numbers. I’ve recently been doing some work on Idris and wondered how easy it would be to translate my Agda proof to Idris.
The original Agda code looked something like this:
module EvenOdd where
open import Data.Nat
data Even : ℕ → Set where
evenZero : Even 0
evenSuc : {n : ℕ} → Even n → Even (suc (suc n))
_e+e_ : {n m : ℕ} → Even n → Even m → Even (n + m)
evenZero e+e b = b
evenSuc a e+e b = evenSuc (a e+e b)
The direct Idris translation looks like:
module EvenOdd
data Even : Nat -> Type where
evenZ : Even Z
evenS : Even n -> Even (S (S n))
total
ee : Even n -> Even m -> Even (n + m)
ee evenZ m = m
ee (evenS n) m = evenS (ee n m)
The few differences:
We don’t have to import a Nat type
Totality is not the default (there is a flag to make it so, though)
We can’t define mixed letter and symbol operators
Pretty easy! Time for something trickier. Now, I haven’t done very much type level Haskell but I wanted to see how easy it would be to translate to the recent GHC 7.8 release.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
module EvenOdd where
data Nat = Z | S Nat
data Even :: Nat -> * where
EvenZ :: Even Z
EvenS :: Even n -> Even (S (S n))
type family Plus (n :: Nat) (m :: Nat) :: Nat
type instance Plus Z m = m
type instance Plus (S n) m = S (Plus n m)
ee :: Even n -> Even m -> Even (Plus n m)
ee EvenZ m = m
ee (EvenS n) m = EvenS (ee n m)
Getting a bit trickier. We’ve had to do the following:
Enable data type promotion, so that data types can be kinds
Enable type families, so we can write a type level functions
Define our own Nat and use that as a kind
Define our own Plus type level function (the type family)
Get totality results as warnings (or errors via
-Wall
)
The few problems are caused by Haskell’s distinction between values, types and kinds. Everything else looks extremely similar - we’ve been lucky to fall into an area where the GHC data kinds extension works really well and we can promote our simple Nat type to a kind.
Let’s step it right up. Now let’s encode this in Scala. Are you ready?
package org.brianmckenna.evenodd
sealed trait Nat
trait Z extends Nat
case object Z extends Z
case class S[N <: Nat](n: N) extends Nat
sealed trait Even[N <: Nat]
trait EvenZ extends Even[Z]
case object EvenZ extends EvenZ
case class EvenS[N <: Nat](n: Even[N]) extends Even[S[S[N]]]
object Even {
implicit val evenZ = EvenZ
implicit def evenS[N <: Nat](implicit even: Even[N]) = EvenS[N](even)
}
sealed trait Plus[N <: Nat, M <: Nat] {
type Result <: Nat
}
object Plus {
type Aux[N <: Nat, M <: Nat, R <: Nat] = Plus[N, M] {
type Result = R
}
implicit def plusZ[M <: Nat] = new Plus[Z, M] {
type Result = M
}
implicit def plusS[N <: Nat, M <: Nat](implicit plus: Plus[N, S[M]]) = new Plus[S[N], M] {
type Result = plus.Result
}
}
object ee {
def apply[N <: Nat, M <: Nat, R <: Nat](n: Even[N], m: Even[M])(implicit sum: Plus.Aux[N, M, R], re: Even[R]) = re
}
Now, this probably looks pretty verbose since we have to define our own type level Nat and Plus function. The type level Plus uses Scala’s path-dependent types.
What’s interesting is that our theorem is expressed as a constraint
that given Even[N]
and Even[M]
then we can construct an Even[N + M]
from an implicit. What we’ve given up is a constructive proof that
every combination of two even numbers always results in an even number
(but we know from our previous proofs that it’s true) - specifically,
we can’t tell if the the implicit re
can be found for all values. We
could fix this by splitting the implicit up into each case but we
can’t get totality checking.
Hopefully this gives a very quick feel for programming at the type level in Agda, Idris, Haskell and Scala. Type level programming in Agda and Idris is just as easy as programming at the value level. Type level programming in Haskell and Scala is a bit annoying, we have to write functions very differently at the type level and value level, but it’s impressive that we can achieve our goal in much more widely used languages.
Thanks to Miles Sabin for help with simplifying the Scala version.