I have the following toy implementation of a non-empty list (NEList
) datatype:
-- A type to describe whether or not a list is empty.
data Emptiness :: Type where
Empty :: Emptiness
NotEmpty :: Emptiness
-- The list itself. Note the existential type in `Cons'.
data List :: Emptiness -> Type -> Type where
Nil :: List 'Empty a
Cons :: a -> List e a -> List 'NotEmpty a
type EList a = List 'Empty a
type NEList a = List 'NotEmpty a
For example it's very easy to define a 'safe head' function that only operates on non-empty lists:
eHead :: NEList a -> a
eHead (Cons a _) = a
The last is similarly easy, but has a little complication:
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b@(Cons _ _)) = eLast b
The reason the pattern needs to be like this is to convince GHC that the type of b
is indeed List 'NotEmpty
, instead of an unknown existential type. The following code fails for that reason: (Couldn't match type ‘e’ with ‘'NotEmpty’ ...
)
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b) = eLast b
I'm fully aware why this is happening. What I'd like to know is, can I avoid having to write b@(Cons _ _)
every time? Is there some other way I can restrict the type, so that GHC knows that b
is referring strictly to something of type List 'NotEmpty
?
One obvious way is to use unsafeCoerce
but this defeats the point of the exercise.
For the sake of reproducibility, here is my preamble:
{-# OPTIONS_GHC -Wall -Werror #-} -- To prevent missed cases.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
import Data.Kind
tail :: [a] -> [a]
orlast :: [a] -> a
? – Lydalydditelast
nottail
. Let me fix that. Though I think defining an analogue totail
might be very interesting too! – ComparativeeLast
... – Lydalydditetail
may be harder than you expect. It's minorly tricky (though at least by now a well-known trick) to even write the type fortail
. – Meridethmeridianexists e. List e a
->(EList a -> b, NEList a -> b) -> b
) and existential wrappers (data SomeList a where SomeList :: List e a -> SomeList a
; sometimes paired with a GADT that lets you recovere
). – Meridethmeridian