Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically generate Haskell types at runtime?

Tags:

types

haskell

Can one define a Haskell type at runtime from a given template? Here's what I mean by this. Suppose I need an integer type that is restricted to some range (unknown precisely at compile time). I also want a feature that:

succ 0 = 1
succ 1 = 2
...
succ n = 0

n being unknown at compile time. I could do something like this:

data WrapInt = WrapInt {
        value       :: Int,
        boundary    :: Int
}

wrapInt :: Int -> Int -> WrapInt
wrapInt boundary value = WrapInt value boundary

Now what I would like to have is to preserve the wrapInt function as it is, but to avoid storing the boundary as a value inside WrapInt type. Instead I would like it to be stored somehow in type definition, which of course means that the type would have to be defined dynamically at runtime.

Is it possible to achieve this in Haskell?

like image 874
Sventimir Avatar asked Apr 27 '15 18:04

Sventimir


1 Answers

The reflection package lets you generate new "local" instances of a typeclass at runtime.

For example, suppose we have the following typeclass of values that can "wrap around":

{-# LANGUAGE Rank2Types, FlexibleContexts, UndecidableInstances #-}

import Data.Reflection
import Data.Proxy

class Wrappy w where
   succWrappy :: w -> w

We define this newtype that carries a phantom type parameter:

data WrapInt s = WrapInt { getValue :: Int } deriving Show

An make it an instance of Wrappy:

instance Reifies s Int => Wrappy (WrapInt s) where
    succWrappy w@(WrapInt i) = 
        let bound = reflect w 
        in
        if i == bound
            then WrapInt 0
            else WrapInt (succ i)

The interesting part is the Reifies s Int constraint. It means: "the phantom type s represents a value of type Int at the type level". Users never define an instance for Reifies, this is done by the internal machinery of the reflection package.

So, Reifies s Int => Wrappy (WrapInt s) means: "whenever s represent a value of type Int, we can make WrapInt s an instance of Wrappy".

The reflect function takes a proxy value that matches the phantom type and brings back an actual Int value, which is used when implementing the Wrappy instance.

To actually "assign" a value to the phantom type, we use reify:

-- Auxiliary function to convice the compiler that
-- the phantom type in WrapInt is the same as the one in the proxy
likeProxy :: Proxy s -> WrapInt s -> WrapInt s
likeProxy _ = id

main :: IO ()
main = print $ reify 5 $ \proxy -> 
    getValue $ succWrappy (likeProxy proxy (WrapInt 5))

Notice that the signature of reify forbids the phantom type from escaping the callback, that's why we must unwrap the result with getValue.

See more examples in this answer, on in the reflection GitHub repo.

like image 74
danidiaz Avatar answered Nov 09 '22 23:11

danidiaz