I came up with a nice exercise, but can't make it work.
The idea is to try and express Roman numerals in such a way that the type checker will tell me whether the numeral is valid.
{-# LANGUAGE RankNTypes
, MultiParamTypeClasses #-}
data One a b c = One a deriving (Show, Eq)
data Two a b c = Two (One a b c) (One a b c) deriving (Show, Eq)
data Three a b c = Three (One a b c) (Two a b c) deriving (Show, Eq)
data Four a b c = Four (One a b c) (Five a b c) deriving (Show, Eq)
data Five a b c = Five b deriving (Show, Eq)
data Six a b c = Six (Five a b c) (One a b c) deriving (Show, Eq)
data Seven a b c = Seven (Five a b c) (Two a b c) deriving (Show, Eq)
data Eight a b c = Eight (Five a b c) (Three a b c) deriving (Show, Eq)
data Nine a b c d e = Nine (One a b c) (One c d e) deriving (Show, Eq)
data Z = Z deriving (Show, Eq) -- dummy for the last level
data I = I deriving (Show, Eq)
data V = V deriving (Show, Eq)
data X = X deriving (Show, Eq)
data L = L deriving (Show, Eq)
data C = C deriving (Show, Eq)
data D = D deriving (Show, Eq)
data M = M deriving (Show, Eq)
i :: One I V X
i = One I
v :: Five I V X
v = Five V
x :: One X L C
x = One X
l :: Five X L C
l = Five L
c :: One C D M
c = One C
d :: Five C D M
d = Five D
m :: One M Z Z
m = One M
infixr 4 #
class RomanJoiner a b c where
(#) :: a -> b -> c
instance RomanJoiner (One a b c) (One a b c) (Two a b c) where
(#) = Two
instance RomanJoiner (One a b c) (Two a b c) (Three a b c) where
(#) = Three
instance RomanJoiner (One a b c) (Five a b c) (Four a b c) where
(#) = Four
instance RomanJoiner (Five a b c) (One a b c) (Six a b c) where
(#) = Six
instance RomanJoiner (Five a b c) (Two a b c) (Seven a b c) where
(#) = Seven
instance RomanJoiner (Five a b c) (Three a b c) (Eight a b c) where
(#) = Eight
instance RomanJoiner (One a b c) (One c d e) (Nine a b c d e) where
(#) = Nine
main = print $ v # i # i
This possibly can be done differently, and the solution is incomplete, but right now I need to understand why it complains that there is no instance for RomanJoiner (One I V X) (One I V X) b0, whereas I think I declared such a joiner.