I've recently been playing with Sebastian Fischer's explicit sharing monad
library (found at http://sebfisch.github.com/explicit-sharing) and after
looking at the code noticed the implementation memoizes values into an IntMapof untyped values that are coerced into the correct type on retrieval, which
is a sensible and practical way to implement this.Just for fun I thought I'd have a go at implementing a (very impractical)
version without type casting but, disappointingly, I didn't quite get there.I ran out of steam by the time I got around to playing with parameterized
monads by which stage the type signatures started looking a bit scary.That and I needed to get back to my projects.
This has been sitting on my hard drive for a couple of weeks now and I've
finally decided to clean it up and post it, maybe someone else will find itamusing.
The post is about HList inspired type level implementation of Okasaki's
skew binary random access lists indexed by Roman numerals, using ideas fromRalf Hinze's paper "Functional Pearl: Typed Quote/Antiquote - Or: Compile-time
Parsing" and some fun tricks from the MLton wiki:http://www.cs.ox.ac.uk/ralf.hinze/publications/Quote.pdf
http://mlton.org/Foldhttp://mlton.org/Fold01N
http://mlton.org/VariableArityPolymorphism
With that out of the way, we start of by declaring all the differentextensions we'll need.
> {-# LANGUAGE FlexibleInstances #-}> {-# LANGUAGE FunctionalDependencies #-}
> {-# LANGUAGE MultiParamTypeClasses #-}> {-# LANGUAGE ScopedTypeVariables #-}
> {-# LANGUAGE TypeFamilies #-}> {-# LANGUAGE TypeSynonymInstances #-}
These two are ugly. We need undecidable instances for bunch of things and
overlapping instances for the type equality trick borrowed from the HListpaper:
http://homepages.cwi.nl/~ralf/HList/paper.pdf
> {-# LANGUAGE OverlappingInstances #-}> {-# LANGUAGE UndecidableInstances #-}
For GHC 7 we also need to increase the context stack.
> {-# OPTIONS_GHC -fcontext-stack=100 #-}Define Boolean values at the type level.
> data T = T> data F = F
And now Peano numbers. We don't actually use the constructors for
anything but if we omitted them we'd have to use another extension,EmptyDataDecls.
> data Z = Z> data S n = S n
Here we create some types for heterogeneous lists and trees. Note that Pair
has an extra type parameter. We provide two different versions of the consoperation. One requires that extra parameter but does not require undecidable
instances. Unfortunately we still need undecidable instances lateron for indexing.
> data Pair i x y = Pair x y deriving Show> data Nil = Nil deriving Show
> data Tree l x r = Node l x r deriving Show> data Leaf x = Leaf x deriving Show
>> type Pair' x y = Pair () x y
>> nil :: Nil
> nil = NilEquality, less than, addition, subtraction and multiplication by 2 type
functions for Peano numbers.> type family Equals m n
>> type instance Equals Z Z = T
> type instance Equals Z (S n) = F> type instance Equals (S n) Z = F
> type instance Equals (S m) (S n) = Equals m n>
> type family LessThan m n>
> type instance LessThan Z Z = F> type instance LessThan Z (S n) = T
> type instance LessThan (S n) Z = F> type instance LessThan (S m) (S n) = LessThan m n
>> type family Add m n
>> type instance Add Z n = n
> type instance Add n Z = n> type instance Add (S m) (S n) = S (S (Add m n))
>> type family Sub m n
>> type instance Sub n Z = n
> type instance Sub (S m) (S n) = Sub m n>
> type family Mul2 n>
> type instance Mul2 Z = Z> type instance Mul2 (S n) = S (S (Mul2 n))
And finally tree depth and weight/size.
> type family Depth t>
> type instance Depth (Leaf x ) = S Z> type instance Depth (Tree l x r) = S (Depth l)
>> type family Weight t
>> type instance Weight (Leaf x ) = S Z
> type instance Weight (Tree l x r) = S (Mul2 (Weight l))For reference, here is normal implementation of random access lists
newtype List a = List { unList :: [(Int, Tree a)] } deriving Show
data Tree a = Leaf a | Node (Tree a) a (Tree a) deriving Show
cons :: a -> List a -> List a cons x (List wts) = List $ case wts of
(v, l) : (w, r) : wts' | v == w -> (1 + w + w, Node l x r) : wts' _ -> (1 , Leaf x ) : wts
List here is a list of pairs of balanced binary trees and their weights.
The cons function has exactly two cases. The second case is trivial, we aremostly interested in the first case. If the weights of the two trees at the
front of the list are the same, we combine them into a new tree with the newlyinserted element at the top.
Now we are ready to implement cons. This first version is simpler but requires
undecidable instances.> class Cons' x y z | x y -> z where cons' :: x -> y -> z
Here we cover the cases of inserting an element into an empty list, and
inserting an element into a list with a single element. These cover two out offour different possibilities when inserting a new element.
> instance Cons' x Nil (Pair' (Leaf x) Nil) where cons' = Pair . Leaf
>> instance Cons' x (Pair' y Nil) (Pair' (Leaf x) (Pair' y Nil)) where
> cons' = Pair . LeafThe remaining two cases occur when we have at least two elements in the list.
This is where we need to compare weights of trees. First observation is thatwe don't need weight of a tree, depth is sufficient.
Now we have a problem. We'd like to be able to write something like
instance (Depth l ~ u, Depth r ~ v, Equals u v ~ T) => Cons' x (Pair' l (Pair' r y)) (Pair' (Tree l x r) y) where ...
instance (Depth l ~ u, Depth r ~ v, Equals u v ~ F) => Cons' x y (Pair' (Leaf x) y) where ...
but we get a functional dependency conflict in the second instance as the type
variable y might stand for Pair' l (Pair' r y) and the functional dependencyalready says the result should be Pair' (Tree l x r) y, not
Pair' (Leaf x) (Pair' l (Pair' r y))We need to merge these two instances into one and then branch on the result of
the comparison of tree depths.To do this, we introduce another type class to do the branching.
> class ConsIf c x y z | c x y -> z where consIf :: c -> x -> y -> z
And merge the instances.
> instance ( Depth l ~ u> , Depth r ~ v
> , Equals u v ~ c> , ConsIf c x (Pair' l (Pair' r y)) z ) =>
>> Cons' x (Pair' l (Pair' r y)) z where
>> cons' = consIf (undefined :: c)
>> instance ConsIf F x y (Pair' (Leaf x) y) where consIf _ = Pair . Leaf
>> instance (Depth l ~ u, Depth r ~ u) =>
>> ConsIf T x (Pair' l (Pair' r y)) (Pair' (Tree l x r) y) where
>> consIf _ x (Pair l (Pair r y)) = Pair (Node l x r) y
We can now test our cons function.
> t0' = cons' 'a'> $ cons' (33 :: Int)
> $ cons' (4.4 :: Float)> $ cons' (88 :: Integer)
> $ cons' ([1 .. 8] :: [Int])> $ cons' "moose"
> $ cons' ((6, 3) :: (Int, Int))> $ nil
And ghci tells us the type is
*Main> :t t0't0'
:: Pair' (Tree
(Tree (Leaf Float) Int (Leaf Integer)) Char
(Tree (Leaf [Char]) [Int] (Leaf (Int, Int)))) Nil
Now we get to the second version that doesn't need the undecidable instances
extension. In this one we go a step back and attempt to keep track of depthinformation in the type of the list. When we again end up at the point where
we need to compare if two trees at the front of the list are of the samedepth, we run into a problem.
instance Cons x (Pair i l (Pair i r y)) (Pair (S i) (Tree l x r) y) where ...
instance Cons x (Pair i y (Pair j z w)) (Pair (S Z) x (Pair i y (Pair j z w))) where ...
This clearly won't work as we again get a functional dependency conflict. The j
type variable could be the same as i in which case we have two different rulesfor what the result of consing x with (Pair i l (Pair i r y)) should be.
As we don't want to use undecidable instances, we are stuck. Unless we canfind a way to disambiguate the two instances.
Instead of keeping track of depth information, we could try to track how many
elements we need to complete a new tree. That way instead of counting up, weare counting down towards zero and it becomes easy to differentiate the two
instances as only the one where the depths of the two trees are equal willcontain a Z.
Keeping track of how many elements are missing turns out to be a problem,
at least as long as we don't want to use undecidable instances. Fortunatelywe can replace the number of elements missing with the number of levels the
tree at the front needs before its depth matches the tree that follows.
> class Cons x y z | x y -> z where cons :: x -> y -> zInserting an element into an empty list involves adding the element to the
front and setting its the depth counter to 1.> instance Cons x Nil (Pair (S Z) (Leaf x) Nil) where cons = Pair . Leaf
If we are inserting an element into a list that already has a single tree,
we again add the new element to the front, set its counter to 1 and wedecrement the counter of the tree that now follows the newly inserted element.
> instance Cons x (Pair (S i) y Nil) (Pair (S Z) (Leaf x) (Pair i y Nil)) where
> cons x (Pair y z) = Pair (Leaf x) (Pair y z)Here we have the case where the list already contains at least two trees but
their depths are not equal. Like with the previous instance we insert the newelement to the front and decrement the counter of the tree that now follows it.
> instance Cons x (Pair (S i) y (Pair (S j) z w))> (Pair (S Z) (Leaf x) (Pair i y (Pair (S j) z w))) where
>> cons x (Pair y z) = Pair (Leaf x) (Pair y z)
That means we are left with one more case to handle. The case when the depths
of the two trees are the same, or rather when the counter reaches 0.As we merge the two trees and create a new tree, we must also decrement the
counter of the tree that follows. What this means is that we really need twoinstances. One where there are only two trees of the same depth in the list,
and one where those two trees are followed by at least one more tree.
> instance (Depth l ~ i, Depth r ~ i) =>> Cons x (Pair i l (Pair Z r Nil)) (Pair (S i) (Tree l x r) Nil) where
>> cons x (Pair l (Pair r y)) = Pair (Node l x r) y
As we merge the trees into a new tree, we reset and increment the depth
counter. Note that we are using the first tree's counter. Reason for that isthat the counter must represent the tree's depth as the tree at the front was
either just inserted and so its counter is 1, or it was created when twosmaller trees merged and its counter was set to its depth.
Finally we must also decrement the counter of the tree that follows.
> instance (Depth l ~ i, Depth r ~ i) =>> Cons x (Pair i l (Pair Z r (Pair (S j) y z)))
> (Pair (S i) (Tree l x r) (Pair j y z)) where>
> cons x (Pair l (Pair r (Pair y z))) = Pair (Node l x r) (Pair y z)
Now we can test the new cons function.> t0 = cons 'a'
> $ cons (33 :: Int)> $ cons (4.4 :: Float)
> $ cons (88 :: Integer)> $ cons ([1 .. 8] :: [Int])
> $ cons "moose"> $ cons ((6, 3) :: (Int, Int))
> $ nilThe type is slightly more verbose.
*Main> :t t0t0
:: Pair (S (S (S Z)))
(Tree (Tree (Leaf Float) Int (Leaf Integer))
Char (Tree (Leaf [Char]) [Int] (Leaf (Int, Int))))
NilHere we introduce our wrapper for Roman numerals though we won't get to
play with those just yet.> newtype R n = R { unR :: Int } deriving Show
We want to get indexing and updates out of the way first. The reference
implementation for the indexing function is a bit more complex than the consfunction. Each of the cases we need to handle has been marked with a comment
so we can see which instance matches which part of the code. index :: List a -> Int -> Maybe a
index (List ((w, t) : wts)) i | i < w = index' w i t -- 1
| otherwise = index (List wts) (i - w) -- 2 where
index' 1 0 (Leaf x ) = Just x -- 3 index' _ _ (Leaf _ ) = Nothing
index' w i (Node l x r) | i == 0 = Just x -- 4
| i <= w' = index' w' (i - 1 ) l -- 5 | otherwise = index' w' (i - 1 - w') r -- 6
where w' = w `div` 2
index _ _ = Nothing
We don't need to do anything when the index is out of range - the type systemtakes care of that for us.
> class Index i x y | i y -> x where index :: y -> R i -> x
> class IndexIf c i x y | c i y -> x where indexIf :: c -> y -> R i -> x
First we handle the easy cases.> instance Index Z x (Leaf x ) where index (Leaf x ) _ = x -- 3
> instance Index Z x (Tree l x r) where index (Node _ x _) _ = x -- 4
Cases 5 and 6 require the use of a second type class so we can branch on theresult of the comparison i <= w, though we rewrite it to w < i' + 1 to make
use of the LessThan type family and to differentiate instance 6 frominstance 4.
Instead of halving the weight of the tree we are currently working on, we
get the weight of one of its children.> instance ( Weight l ~ w
> , LessThan w (S i) ~ c> , IndexIf c i x (Tree l y r) ) => Index (S i) x (Tree l y r) where
>> index t _ = indexIf (undefined :: c) t (undefined :: R i) -- 5, 6
>> instance Index i x l => IndexIf F i x (Tree l y r) where
> indexIf _ (Node l _ _) = index l -- 5>
> instance ( Weight r ~ w> , Sub i w ~ j
> , Index j x r ) => IndexIf T i x (Tree l y r) where>
> indexIf _ (Node _ _ r) _ = index r (undefined :: R j) -- 6
Cases 1 and 2 are handled in a similar way.> instance ( Weight t ~ w
> , LessThan i w ~ c> , IndexIf c i x (Pair j t y) ) => Index i x (Pair j t y) where
>> index = indexIf (undefined :: c) -- 1, 2
>> instance Index i x t => IndexIf T i x (Pair j t y) where
> indexIf _ (Pair t _) = index t -- 1>
> instance ( Weight t ~ w> , Sub i w ~ j
> , Index j x y ) => IndexIf F i x (Pair k t y) where>
> indexIf _ (Pair _ y) _ = index y (undefined :: R j) -- 2
Update is similar. update :: Int -> a -> List a -> List a
update i x (List ((w, t) : wts)) = List $ if i < w then (w, update' w i t) : wts -- 1
else (w, t) : unList (update (i - w) x (List wts)) -- 2 where
update' 1 0 (Leaf _ ) = Leaf x -- 3 update' _ _ (Leaf _ ) = error "update"
update' w i (Node l x' r) | i == 0 = Node l x r -- 4
| i <= w' = Node l' x' r -- 5 | otherwise = Node l x' r' -- 6
where w' = w `div` 2
l' = update' w' (i - 1 ) l r' = update' w' (i - 1 - w') r
update _ _ _ = error "update"
> class Index i x z => Update i x y z | i x y -> z where> update :: y -> R i -> x -> z
>> class UpdateIf c i x y z | c i x y -> z where
> updateIf :: c -> y -> R i -> x -> z>
> instance Update Z x (Leaf y) (Leaf x) where> update _ _ x = Leaf x -- 3
>> instance Update Z x (Tree l y r) (Tree l x r) where
> update (Node l _ r) _ x = Node l x r -- 4>
> instance ( Weight l ~ w> , LessThan w (S i) ~ c
> , Index (S i) x t> , UpdateIf c i x (Tree l y r) t) =>
>> Update (S i) x (Tree l y r) t where
>> update t _ = updateIf (undefined :: c) t (undefined :: R i) -- 5, 6
>> instance Update i x l l' => UpdateIf F i x (Tree l y r) (Tree l' y r) where
> updateIf _ (Node l y r) i x = Node (update l i x) y r -- 5>
> instance ( Weight r ~ w> , Sub i w ~ j
> , Update j x r r' ) =>>
> UpdateIf T i x (Tree l y r) (Tree l y r') where>
> updateIf _ (Node l y r) _ x = -- 6> Node l y (update r (undefined :: R j) x)
>> instance ( Weight t ~ w
> , LessThan i w ~ c> , Index i x p
> , UpdateIf c i x (Pair j t y) p ) => Update i x (Pair j t y) p where>
> update = updateIf (undefined :: c) -- 1, 2>
> instance Update i x t t' => UpdateIf T i x (Pair j t y) (Pair j t' y) where> updateIf _ (Pair t y) i x = Pair (update t i x) y -- 1
>> instance ( Weight t ~ w
> , Sub i w ~ j> , Update j x y z ) => UpdateIf F i x (Pair k t y) (Pair k t z) where
>> updateIf _ (Pair t y) _ x = -- 2
> Pair t (update y (undefined :: R j) x)Maybe we could combine Update and Index type classes into a single class? But
we won't attempt that here.Now that we have indexing and updates, we need values we can use to index the
list. Typing Z, S Z, S (S Z) and so on is too tedious and not very readable.We could use templates to predefine a whole bunch of type level numbers.
That's kind of ugly though. And we can do it without using any extensions, inplain Haskell 98.
We start off by making I a type synonym for S. Then we define types II to X,
XX to C in steps of 10, CC to M in steps of 100 and finally we define MM andMMM. I don't know how Roman numerals are supposed to work above 3999 so that's
the largest number we will be able to encode. Probably for the best as thetype system doesn't seem too happy about having to deal with types of this
sort.> type I n = S n
>> type II n = I (I n)
> type III n = I (II n)> type IV n = I (III n)
> type V n = I (IV n)> type VI n = I (V n)
> type VII n = I (VI n)> type VIII n = I (VII n)
> type IX n = I (VIII n)> type X n = I (IX n)
>> type XX n = X (X n)
> type XXX n = X (XX n)> type XL n = X (XXX n)
> type L n = X (XL n)> type LX n = X (L n)
> type LXX n = X (LX n)> type LXXX n = X (LXX n)
> type XC n = X (LXXX n)> type C n = X (XC n)
>> type CC n = C (C n)
> type CCC n = C (CC n)> type CD n = C (CCC n)
> type D n = C (CD n)> type DC n = C (D n)
> type DCC n = C (DC n)> type DCCC n = C (DCC n)
> type CM n = C (DCCC n)> type M n = C (CM n)
>> type MM n = M (M n)
> type MMM n = M (MM n)Now for the fun part. We'd like to be able to construct only valid Roman
numerals and that the type signatures match the values. We start of bydefining the return and lift functions as described in Ralf Hinze's paper.
We don't go all the way to creating a monad as these two functions are theonly thing we need.
> ret a f = f a> lift f = ret . f
Now before we continue, we need a way to ensure that only valid numbers are
constructed. We can group numbers in the range I to X into one group, let'scall it ones, X to C into group tens, C to M into group hundreds and M, MM and
MMM are in group thousands. A valid number can have at most 1 digit from eachgroup. Digits also have to be ordered from largest to smallest, left to right.
To do that, we could try and reuse the LessThen type family from before and add
constraints so that a digit from the tens group can only be appended to anumber less than ten. And a digit from the hundreds group can only be applied
to a number less than a hundred. This approach works but it makes the GHC'stype system choke and evaluating types of expressions takes minutes.
We could also create a class for each of the groups and a set of combinators
that can only be applied to a group of a lower rank. This works better.
But we can go a step further than that - we don't actually need any typeclasses. All we need is one more phantom type for the rank of the number.
So we introduce a second type that resembles R, only has one extra typevariable.
> newtype R' n s = R' { unR' :: Int } deriving Show
Here we make use of ret and lift to create polyvariadic functions forconstructing Roman numerals. rn starts the construction and nr finalizes it.
> rn :: ((R' n s -> R n) -> r) -> r> rn f = ret (R . unR') f
>> nr :: (R' Z (IV Z) -> r) -> r
> nr f = f (R' 0)Trying them in ghci we get
*Main> rn nrR {unR = 0}
*Main> :t rn nrrn nr :: R Z
We have a zero. Notice in the type signature for nr, rank of zero is 4.
Now we need to generate the digits. We can use a single function to generate
all of them.> mkR n = lift (\f -> f . R' . (+ n) . unR')
And we encode the constraints in the type. Digits from the ones group can only
be applied to a number of rank 4. There is only one number of rank 4 and thatis zero.
Applying a digit from the ones group to a zero gives us a number of rank 3.Digits from the tens group can be applied to numbers of ranks 3 or 4 and
return a number of rank 2. And so on.> type RF n m s t k r = (R' n s -> k) -> ((R' m t -> k) -> r) -> r
>> i :: RF (I n) n (III Z) (IV s) k r
> ii :: RF (II n) n (III Z) (IV s) k r> iii :: RF (III n) n (III Z) (IV s) k r
> iv :: RF (IV n) n (III Z) (IV s) k r> v :: RF (V n) n (III Z) (IV s) k r
> vi :: RF (VI n) n (III Z) (IV s) k r> vii :: RF (VII n) n (III Z) (IV s) k r
> viii :: RF (VIII n) n (III Z) (IV s) k r> ix :: RF (IX n) n (III Z) (IV s) k r
>> x :: RF (X n) n (II Z) (III s) k r
> xx :: RF (XX n) n (II Z) (III s) k r> xxx :: RF (XXX n) n (II Z) (III s) k r
> xl :: RF (XL n) n (II Z) (III s) k r> l :: RF (L n) n (II Z) (III s) k r
> lx :: RF (LX n) n (II Z) (III s) k r> lxx :: RF (LXX n) n (II Z) (III s) k r
> lxxx :: RF (LXXX n) n (II Z) (III s) k r> xc :: RF (XC n) n (II Z) (III s) k r
>> c :: RF (C n) n (I Z) (II s) k r
> cc :: RF (CC n) n (I Z) (II s) k r> ccc :: RF (CCC n) n (I Z) (II s) k r
> cd :: RF (CD n) n (I Z) (II s) k r> d :: RF (D n) n (I Z) (II s) k r
> dc :: RF (DC n) n (I Z) (II s) k r> dcc :: RF (DCC n) n (I Z) (II s) k r
> dccc :: RF (DCCC n) n (I Z) (II s) k r> cm :: RF (CM n) n (I Z) (II s) k r
>> m :: RF (M n) n Z (I s) k r
> mm :: RF (MM n) n Z (I s) k r> mmm :: RF (MMM n) n Z (I s) k r
>> i = mkR 1
> ii = mkR 2> iii = mkR 3
> iv = mkR 4> v = mkR 5
> vi = mkR 6> vii = mkR 7
> viii = mkR 8> ix = mkR 9
>> x = mkR 10
> xx = mkR 20> xxx = mkR 30
> xl = mkR 40> l = mkR 50
> lx = mkR 60> lxx = mkR 70
> lxxx = mkR 80> xc = mkR 90
>> c = mkR 100
> cc = mkR 200> ccc = mkR 300
> cd = mkR 400> d = mkR 500
> dc = mkR 600> dcc = mkR 700
> dccc = mkR 800> cm = mkR 900
>> m = mkR 1000
> mm = mkR 2000> mmm = mkR 3000
We can now try to construct a number such as 2011.
> r2011 = rn mm x i nr*Main> r2011
R {unR = 2011}*Main> :t r2011
r2011 :: R (MM (X (I Z)))The type reflects the value we entered.
Attempting to construct an invalid number gives us an error
*Main> rn v vi nr<interactive>:1:0:
Couldn't match expected type `Z' against inferred type `S s' Probable cause: `rn' is applied to too many arguments
In the expression: rn v vi nr In the definition of `it': it = rn v vi nr
I'll finish off this post with a generic map function. Here we use the type
equality trick from the HList paper.> class GMap t p q | t p -> q where gmap :: t -> p -> q
> class GMapIf c t p q | c t p -> q where gmapIf :: c -> t -> p -> q>
> instance GMap (a -> b) Nil Nil where gmap _ = id>
> instance ( TypeEq a x c> , GMapIf c (a -> b) (Leaf x) (Leaf y) ) =>
> > GMap (a -> b) (Leaf x) (Leaf y) where gmap = gmapIf (undefined :: c)
>> instance ( TypeEq a x c
> , GMapIf c (a -> b) (Tree l x r) (Tree l' y r') ) =>>
> GMap (a -> b) (Tree l x r) (Tree l' y r') where>
> gmap = gmapIf (undefined :: c)>
> instance ( GMap (a -> b) x y> , GMap (a -> b) p q ) =>
>> GMap (a -> b) (Pair i x p) (Pair i y q) where
>> gmap f (Pair x p) = Pair (gmap f x) (gmap f p)
>> instance GMapIf F (a -> b) (Leaf x) (Leaf x) where gmapIf _ _ = id
>> instance GMapIf T (a -> b) (Leaf a) (Leaf b) where
> gmapIf _ f (Leaf a) = Leaf (f a)>
> instance ( GMap (a -> b) l l'> , GMap (a -> b) r r' ) =>
>> GMapIf F (a -> b) (Tree l x r) (Tree l' x r') where
>> gmapIf _ f (Node l x r) = Node (gmap f l) x (gmap f r)
>> instance ( GMap (a -> b) l l'
> , GMap (a -> b) r r' ) =>>
> GMapIf T (a -> b) (Tree l a r) (Tree l' b r') where>
> gmapIf _ f (Node l a r) = Node (gmap f l) (f a) (gmap f r)>
> type family Cast a> type family Cast' t a
> type family Cast'' t a>
> type instance Cast a = Cast' () a> type instance Cast' t a = Cast'' t a
> type instance Cast'' () a = a>
> class TypeEq x y c | x y -> c>
> instance Cast c ~ F => TypeEq x y c>
> instance TypeEq Char Char T> instance TypeEq Int Int T
> instance TypeEq Float Float T> instance TypeEq a a T => TypeEq [a] [a] T
And a few tests to see if it all works.
> t1 = gmap (length :: String -> Int) t0> t2 = gmap (length :: [Int] -> Int) t1
> t3 = gmap ((+ 1000) :: Int -> Int) t2>
> t4 = cons (t0 `index` rn iv nr) t3>
> t5 = update t4 (rn iv nr) "asdf">
> t1' = gmap (length :: String -> Int) t0'> t2' = gmap (length :: [Int] -> Int) t1'
> t3' = gmap ((+ 1000) :: Int -> Int) t2'>
> t4' = cons' (t0' `index` rn iv nr) t3'>
> t5' = update t4' (rn iv nr) "asdf"As a bonus the implementation of type-level Roman numerals in Standard ML.
SML/NJ has a fit trying to load the code. It seems to cope with it ifconstructors for values above 100 are commented out. And it gives really nice
type signatures. MLton has no problems compiling the code, but fully expandsall type signatures.
(*****************************************************************************)
signature ROMAN = sig
type z type 'n i
type 'n ii = 'n i i type 'n iii = 'n i ii
type 'n iv = 'n i iii type 'n v = 'n i iv
type 'n vi = 'n i v type 'n vii = 'n i vi
type 'n viii = 'n i vii type 'n ix = 'n i viii
type 'n x = 'n i ix type 'n xx = 'n x x
type 'n xxx = 'n x xx type 'n xl = 'n x xxx
type 'n l = 'n x xl type 'n lx = 'n x l
type 'n lxx = 'n x lx type 'n lxxx = 'n x lxx
type 'n xc = 'n x lxxx type 'n c = 'n x xc
type 'n cc = 'n c c type 'n ccc = 'n c cc
type 'n cd = 'n c ccc type 'n d = 'n c cd
type 'n dc = 'n c d type 'n dcc = 'n c dc
type 'n dccc = 'n c dcc type 'n cm = 'n c dccc
type 'n m = 'n c cm type 'n mm = 'n m m
type 'n mmm = 'n m mm type 'n r
type ('n, 's) r' val toInt : 'n r -> int
val <| : ((('n, 's) r' -> 'n r) -> 'r) -> 'r
val |> : ((z, z iv) r' -> 'r) -> 'r type ('n, 'm, 's, 't, 'k, 'r) rf =
(('n, 's) r' -> 'k) -> ((('m, 't) r' -> 'k) -> 'r) -> 'r
val i : ('n i , 'n, z iii, 's iv , 'k, 'r) rf
val ii : ('n ii , 'n, z iii, 's iv , 'k, 'r) rf val iii : ('n iii , 'n, z iii, 's iv , 'k, 'r) rf
val iv : ('n iv , 'n, z iii, 's iv , 'k, 'r) rf val v : ('n v , 'n, z iii, 's iv , 'k, 'r) rf
val vi : ('n vi , 'n, z iii, 's iv , 'k, 'r) rf val vii : ('n vii , 'n, z iii, 's iv , 'k, 'r) rf
val viii : ('n viii, 'n, z iii, 's iv , 'k, 'r) rf val ix : ('n ix , 'n, z iii, 's iv , 'k, 'r) rf
val x : ('n x , 'n, z ii , 's iii, 'k, 'r) rf
val xx : ('n xx , 'n, z ii , 's iii, 'k, 'r) rf val xxx : ('n xxx , 'n, z ii , 's iii, 'k, 'r) rf
val xl : ('n xl , 'n, z ii , 's iii, 'k, 'r) rf val l : ('n l , 'n, z ii , 's iii, 'k, 'r) rf
val lx : ('n lx , 'n, z ii , 's iii, 'k, 'r) rf val lxx : ('n lxx , 'n, z ii , 's iii, 'k, 'r) rf
val lxxx : ('n lxxx, 'n, z ii , 's iii, 'k, 'r) rf val xc : ('n xc , 'n, z ii , 's iii, 'k, 'r) rf
val c : ('n c , 'n, z i , 's ii , 'k, 'r) rf
val cc : ('n cc , 'n, z i , 's ii , 'k, 'r) rf val ccc : ('n ccc , 'n, z i , 's ii , 'k, 'r) rf
val cd : ('n cd , 'n, z i , 's ii , 'k, 'r) rf val d : ('n d , 'n, z i , 's ii , 'k, 'r) rf
val dc : ('n dc , 'n, z i , 's ii , 'k, 'r) rf val dcc : ('n dcc , 'n, z i , 's ii , 'k, 'r) rf
val dccc : ('n dccc, 'n, z i , 's ii , 'k, 'r) rf val cm : ('n cm , 'n, z i , 's ii , 'k, 'r) rf
val m : ('n m , 'n, z , 's i , 'k, 'r) rf
val mm : ('n mm , 'n, z , 's i , 'k, 'r) rf val mmm : ('n mmm , 'n, z , 's i , 'k, 'r) rf
endstructure Roman :> ROMAN = struct
datatype z = Z datatype 'n i = I of 'n
type 'n ii = 'n i i type 'n iii = 'n i ii
type 'n iv = 'n i iii type 'n v = 'n i iv
type 'n vi = 'n i v type 'n vii = 'n i vi
type 'n viii = 'n i vii type 'n ix = 'n i viii
type 'n x = 'n i ix type 'n xx = 'n x x
type 'n xxx = 'n x xx type 'n xl = 'n x xxx
type 'n l = 'n x xl type 'n lx = 'n x l
type 'n lxx = 'n x lx type 'n lxxx = 'n x lxx
type 'n xc = 'n x lxxx type 'n c = 'n x xc
type 'n cc = 'n c c type 'n ccc = 'n c cc
type 'n cd = 'n c ccc type 'n d = 'n c cd
type 'n dc = 'n c d type 'n dcc = 'n c dc
type 'n dccc = 'n c dcc type 'n cm = 'n c dccc
type 'n m = 'n c cm type 'n mm = 'n m m
type 'n mmm = 'n m mm type 'n r = int
datatype ('n, 's) r' = R of int
type ('n, 'm, 's, 't, 'k, 'r) rf = (('n, 's) r' -> 'k) -> ((('m, 't) r' -> 'k) -> 'r) -> 'r
fun toInt n = n local
fun unR (R n) = n fun ret a f = f a
fun lift f = ret o f fun mkR n k = lift (fn g => g o R o (fn x => x + n) o unR) k
in fun <| f = ret unR f
fun |> f = f (R 0) fun i k = mkR 1 k
fun ii k = mkR 2 k fun iii k = mkR 3 k
fun iv k = mkR 4 k fun v k = mkR 5 k
fun vi k = mkR 6 k fun vii k = mkR 7 k
fun viii k = mkR 8 k fun ix k = mkR 9 k
fun x k = mkR 10 k fun xx k = mkR 20 k
fun xxx k = mkR 30 k fun xl k = mkR 40 k
fun l k = mkR 50 k fun lx k = mkR 60 k
fun lxx k = mkR 70 k fun lxxx k = mkR 80 k
fun xc k = mkR 90 k fun c k = mkR 100 k
fun cc k = mkR 200 k fun ccc k = mkR 300 k
fun cd k = mkR 400 k fun d k = mkR 500 k
fun dc k = mkR 600 k fun dcc k = mkR 700 k
fun dccc k = mkR 800 k fun cm k = mkR 900 k
fun m k = mkR 1000 k fun mm k = mkR 2000 k
fun mmm k = mkR 3000 k end
end