Assume I have encoded the natural numbers in Haskell types, and that I have a way of adding and subtracting from them:
data Zero
data Succ n
-- ...
OK. Yes. Definitely, by threading a numeric type around the recursive instances.
First, some boilerplate:
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
Your nats:
data Zero
data Succ n
A recursive builder for the variadic functions, now with an n argument:
class BuildList n a r | r -> a where
build' :: n -> [a] -> a -> r
A base case: stop when we get to Zero
:
instance BuildList Zero a [a] where
build' _ l x = reverse $ x:l
Otherwise, decrement by one and recurse:
instance BuildList n a r => BuildList (Succ n) a (a->r) where
build' (_ :: Succ n) l x y = build' (undefined :: n) (x:l) y
Now, we only want to loop 3 times, so write that down:
build :: BuildList (Succ (Succ Zero)) a r => a -> r
build x = build' (undefined :: Succ (Succ Zero)) [] x
Done.
Testing:
> build "one" "two" "three" :: [[Char]]
["one","two","three"]
Any less or more are errors:
*Main> build "one" "two" "three" "four" :: [[Char]]
<interactive>:1:1:
No instance for (BuildList Zero [Char] ([Char] -> [[Char]]))
*Main> build "one" "two" :: [[Char]]
<interactive>:1:1:
No instance for (BuildList (Succ Zero) [Char] [[Char]])
Inlining Martijn's code gives a very simple solution:
zero xs = xs
suc n xs x = n (xs ++ [x])
buildList n = n []
Oh, my... FlexibleContexts??? NoMonomorphismRestriction??? Come on, guys, isn't it exactly what TypeFamilies are for?
{-# LANGUAGE TypeFamilies #-}
data Zero = Zero
newtype Succ n = Succ n
zero = Zero
one = Succ zero
two = Succ one
three = Succ two
class BuildList n where
type BL n
buildListPrefix :: n -> ([String] -> [String]) -> BL n
instance BuildList Zero where
type BL Zero = [String]
buildListPrefix Zero h = h []
instance BuildList n => BuildList (Succ n) where
type BL (Succ n) = String -> BL n
buildListPrefix (Succ n) h s = buildListPrefix n (h . (s:))
buildList:: BuildList n => n -> BL n
buildList n = buildListPrefix n id
I see your functional-dependency multiparameter empty-datatype flexibly scoped type variables and raise you a Haskell 98 version! It uses HoleyMonoid which is available on hackage:
{-# LANGUAGE NoMonomorphismRestriction #-}
import Prelude hiding (id, (.))
import Control.Category
import Data.HoleyMonoid
suc n = later (:[]) . n
zero = id
one = suc zero
two = suc one
three = suc two
buildList = run
Testing (feel free to omit any type signatures):
> run three "one" "two" "three"
["one","two","three"]