BAM Weblog

EvenOdd in Agda, Idris, Haskell, Scala

Brian McKenna — 2014-01-23

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))

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 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.

Please enable JavaScript to view the comments powered by Disqus.