Clinton
Clinton

Reputation: 23135

Compile time key -> value mapping

I want to create a map at compile time that maps compile time keys to run time values. Functions should be able to check for those keys and throw a compiler error if all not all of the required keys are present.

This is roughly what I'd like to achieve:

class HasFirst a
  first :: String

class HasMiddle a
  middle :: String

class HasLast a
  last :: String

print_full_name :: (HasFirst a, HasLast a) => a -> String

addFirst :: String -> a -> b
addFirst s x = -- ( Add a first name to x )

emptyName :: -- some empty name

x1 = addFirst "John" $ addLast "Smith" $ emptyName
x2 = addMiddle "Bob" $ addLast "Smith" $ emptyName

main = putStr $ print_full_name x1 -- Compiles
main = putStr $ print_full_name x2 -- No first name so compile error

For those with C++ knowledge, I want roughly what boost::fusion::map does.

I don't need exactly the code above, what is important that I can check parameters at compile time. Whether the mechanism is classes or something else I don't mind.

Is there a package that does this or is it easy to develop this?

Upvotes: 1

Views: 297

Answers (1)

opqdonut
opqdonut

Reputation: 5159

Let me tell you how you can get your example to work using a functional design technique called phantom types:

{-# LANGUAGE EmptyDataDecls #-}

data YES
data NO

data Name first middle last = Name String String String

emptyName :: Name NO NO NO
emptyName = Name "" "" ""

addFirst :: String -> Name first middle last -> Name YES middle last
addFirst s (Name a b c) = Name s b c

addMiddle :: String -> Name first middle last -> Name first YES last
addMiddle s (Name a b c) = Name a s c

addLast :: String -> Name first middle last -> Name first middle YES
addLast s (Name a b c) = Name a b s

printFullName :: Name YES middle YES -> String
printFullName (Name a b c) = a++b++c

We use the three type parameters of Name to track which names have been set. printFullName only accepts names with first and last names. We get a type error when trying to print underdefined names:

*Main> printFullName $ addFirst "John" $ addLast "Smith" $ emptyName
"JohnSmith"
*Main> printFullName $ addFirst "John" $ addMiddle "Edward" $ emptyName

<interactive>:1:16:
    Couldn't match expected type `YES' against inferred type `NO'
      Expected type: Name YES YES YES
      Inferred type: Name YES YES NO
    In the second argument of `($)', namely
        `addFirst "John" $ addMiddle "Edward" $ emptyName'
    In the expression:
          printFullName $ addFirst "John" $ addMiddle "Edward" $ emptyName

Note that this is a very crude encoding, but hopefully it demonstrates the power of phantom types.

Upvotes: 7

Related Questions