4

In Coq I can define a Church encoding for lists of length n:

Definition listn (A : Type) : nat -> Type :=
fun m => forall (X : nat -> Type), X 0 -> (forall m, A -> X m -> X (S m)) -> X m.

Definition niln (A : Type) : listn A 0 :=
fun X n c => n.

Definition consn (A : Type) (m : nat) (a : A) (l : listn A m) : listn A (S m) :=
fun X n c => c m a (l X n c).

Is the type system of Haskell (including its extensions) strong enough to accommodate such definitions? If yes, how?

Bob
  • 1,713
  • 10
  • 23

1 Answers1

4

Sure it is:

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}

import Data.Kind        -- Needed for `Type`

data Nat = Z | S Nat    -- Roll your own...

type List (a :: Type) (n :: Nat) =
  forall (x :: Nat -> Type). x Z -> (forall (m :: Nat). a -> x m -> x (S m)) -> x n

niln :: List a Z
niln = \z _ -> z

consn :: a -> List a n -> List a (S n)
consn a l = \n c -> c a (l n c)

Further proof (for skeptics) of the isomorphism with the usual GADT formulation:

data List' (a :: Type) (n :: Nat) where
  Nil :: List' a Z
  Cons :: a -> List' a m -> List' a (S m)

to :: List' a n -> List a n
to Nil = niln
to (Cons a l) = consn a (to l)

from :: List a n -> List' a n
from l = l Nil Cons
Alec
  • 31,829
  • 7
  • 67
  • 114
  • Doesn't the `forall m` break the static guarantee? Shouldn't it be fixed so that `n ~ S m`? – Fyodor Soikin Mar 09 '19 at 20:27
  • 1
    @FyodorSoikin I don't think so. The inductive case needs to work on any `m` (otherwise, how are you to perform induction?). Note that the right hand side of `type List a (S n)` does not mention `List a n`. – Alec Mar 09 '19 at 20:30
  • I see. My head hurts a bit though. :-) – Fyodor Soikin Mar 09 '19 at 20:33
  • Does it mean that Haskell is a dependently-typed language like Idris? – Bob Mar 09 '19 at 20:41
  • @Bob kind of. You can do most of the useful stuff, but not strictly everything that Idris can. The situation is gradually improving though. – Fyodor Soikin Mar 09 '19 at 20:59
  • @Bob not yet. Some other examples from Coq or Idris have no Haskell equivalent. I'm ill-placed to compare Haskell to Idris/Coq given that I don't really know either. – Alec Mar 09 '19 at 21:00
  • Even in the case where Haskell can do something, the syntax in Idris is usually much simpler; the language itself can fill in the details that Haskell makes you provide explicitly. – chepner Mar 09 '19 at 21:06
  • When I replace Z and S by zero and succ of Church numeral I get an error "Not is scope" :-( `type Nat = forall t. t -> (t -> t) -> t zero :: Nat zero = \z _ -> z succ :: Nat -> Nat succ n = \z s -> s (n z s) type List (a :: Type) (n :: Nat) = forall (x :: Nat -> Type). x zero -> (forall (m :: Nat). a -> x m -> x (succ m)) -> x n` – Bob Mar 09 '19 at 22:32
  • 1
    @Bob that's one play where Haskell is not dependently typed enough: you can't just put any expression (like a church encoded numeral) into a type. – Alec Mar 09 '19 at 22:34
  • 1
    @Bob There are some big differences between real dependently typed languages and Haskell. A major one is that Haskell does not allow lambdas in types, e.g. `x :: F (\n -> ...)` is invalid. Similarly, if `x :: forall (f :: A -> B). ...` we can't write `x (\a -> ...)` and choose the `f` argument in that way. Such `f` can only be chosen to be (roughly) a type constructor, usually defined though a `newtype` or `data`. In Haskell types, `f x = g y` iff `f=g` and `x=y`, which is nonsense in dependent types. – chi Mar 09 '19 at 22:45
  • @chi What are the reasons for these limitations? – Bob Mar 09 '19 at 23:31
  • @Bob Haskell wasn't conceived as a dependently typed language. Some dependly-typed-ish features were ported on top of Haskells existing type system as an extension, but turning Haskell into a fully dependently typed language is probably never going to happen - not least of all because while dependent typing is very useful in some concepts, it doesn't come without [it's own set of tradeoffs](https://www.reddit.com/r/haskell/comments/3zc81v/tradeoffs_of_dependent_types_xpost_from_ridris/) over the approach taken in Haskell – Cubic Mar 10 '19 at 02:00
  • If you need more proof that this works: https://stackoverflow.com/q/50844444/5684257 – HTNW Mar 10 '19 at 08:04
  • 1
    @Bob Mainly, it greatly simplifies type inference. If one calls `foo (Just True)` where `foo :: f a -> ...` we get `f=Maybe` and `a = Bool`. In Coq, there are infinitely many solutions. Further, handling equality between types is made almost automatic when using Haskell GADTs, while in Coq we often need to prove a bunch of easy equations (at least, without some Coq plugins). The "Hasochism" paper describes GADTs vs equality in dependent types very well. – chi Mar 10 '19 at 09:04