14

am hoping some Haskell experts can help clarify something.

Is it possible to define Nat in the usual way (via @dorchard Singleton types in Haskell)

data S n = Succ n 
data Z   = Zero

class Nat n 
instance Nat Z
instance Nat n => Nat (S n)

(or some variant thereof) and then define a LessThan relation such that forall n and m

LessThan Z (S Z)
LessThan n m => LessThan n     (S m)
LessThan n m => LessThan (S n) (S m)

and then write a function with a type like:

foo :: exists n. (LessThan n m) => Nat m -> Nat n
foo (S n) = n
foo Z     = foo Z

I explicitly want to use the "LessThan" in the output type for foo, I realize that one could certainly write something like

foo :: Nat (S n) -> Nat n

but thats not what I'm after.

Thanks!

Ranjit.

Community
  • 1
  • 1
Ranjit Jhala
  • 1,242
  • 8
  • 18
  • 2
    `foo :: exists n...` – really? So you want to allow `foo` to return any type it likes, with the only constraint that it be "less than `m`"? That's not possible in Haskell (not just like that), and rightly so. Or do you rather mean, `foo` can return any type the caller requests, as long as it's less than `m`? – leftaroundabout Jul 10 '13 at 20:58
  • no, not "any" type, thats clearly bogus. I just want to say it returns "some" nat that is "less than m" – Ranjit Jhala Jul 10 '13 at 22:39
  • 2
    "some" seems interchangable with "any" in that sentence. The crucial question is: who decides which type it's going to be? – leftaroundabout Jul 10 '13 at 22:42
  • 1
    No one decides, I just want a spec that says "the output is _some_ nat that is strictly less than the input" (without saying _what_ that number is...) – Ranjit Jhala Jul 10 '13 at 23:34
  • 3
    So deciding the type _is_ up to the function (or the guy how implements it, if you prefer that)? – leftaroundabout Jul 10 '13 at 23:39
  • I am a Haskell noob and wonder what the reason is for declaring `LessThan` as a type rather than as a `lessThan` function? – Code-Apprentice Jul 11 '13 at 01:23
  • 1
    @MonadNewb This is type level programming, which is used for some ultra-cunning type tricks. Ranjit is encoding the integers in _the type system_ as opposed to as data, which is why `LessThan` needs to be in the type system too. It's safe to ignore type level programming until you're very confident with Haskell. – AndrewC Jul 11 '13 at 08:43

1 Answers1

17

Here's one way to implement something similar to what you ask about.

Nat

First note that you define Nat as a class and then use it as a type. I think it makes sense to have it as a type, so let's define it as such.

data Z
data S n

data Nat n where
  Zero :: Nat Z
  Succ :: Nat n -> Nat (S n)

LessThan

We can also define LessThan as a type.

data LessThan n m where
  LT1 :: LessThan Z (S Z)
  LT2 :: LessThan n m -> LessThan n (S m)
  LT3 :: LessThan n m -> LessThan (S n) (S m)

Note that I just toke your three properties and turned them into data constructors. The idea of this type is that a fully normalized value of type LessThan n m is a proof that n is less than m.

Work-around for existentials

Now you ask about:

foo :: exists n. (LessThan n m) => Nat m -> Nat n

But there exists no exists in Haskell. Instead, we can define a datatype just for foo:

data Foo m where
  Foo :: Nat n -> LessThan n m -> Foo m

Note that n is effectively existenially quantified here, because it shows up in the arguments of the data constructor Foo but not in its result. Now we can state the type of foo:

foo :: Nat m -> Foo m

A lemma

Before we can implement the example from the question, we have to prove a little lemma about LessThan. The lemma says that n is less than S n for all n. We prove it by induction on n.

lemma :: Nat n -> LessThan n (S n)
lemma Zero = LT1
lemma (Succ n) = LT3 (lemma n)

Implementation of foo

Now we can write the code from the question:

foo :: Nat m -> Foo m
foo (Succ n) = Foo n (lemma n)
foo Zero = foo Zero
Toxaris
  • 7,156
  • 1
  • 21
  • 37
  • 7
    Just for reference, there is another way to encode existential quantification; namely. `exists n. A` is encoded by `forall r. (forall n. A -> r) -> r` – luqui Jul 11 '13 at 01:07
  • 1
    Superb explanation, very clear; sometimes I want to use the upvote button multiple times. – AndrewC Jul 11 '13 at 10:15
  • `foo Zero` doesn't terminate, which is only to be expected, as `foo` can't be total over `Nat m`, only over `Nat (Succ m)`. – rampion Jul 18 '13 at 17:45
  • Note that this isn't the only such function `:: Nat m -> Foo m` you can define. For example you could also define `lemma' :: Nat n -> LessThan Z (S n) ; lemma' Zero = LT1 ; lemma' (Succ n) = LT2 (lemma' n)` and get `foo' :: Nat m -> Foo m ; foo' (Succ n) = Foo Zero (lemma' n) ; foo' Zero = foo' Zero`, which meets the definition just as well. – rampion Jul 18 '13 at 18:13
  • Now note you can define a function `:: Nat m -> [ Foo m ]` which determines the range for all such foo functions with `next :: Foo m -> Foo (S m) ; next (Foo n lt) = Foo (Succ n) (LT3 lt) ; range :: Nat m -> [ Foo m ] ; range Zero = [] ; range (Succ n) = foo' (Succ n) : map next (range n)` – rampion Jul 18 '13 at 18:14