Brandon.Si(mmons)

code / art / projects

A State Monad With Dynamically-typed State

Haskell’s standard State monad types allow the programmer to define a computation that works with some state in a way that looks and feels similar to using mutable state in an imperative language. However these State monad types are limited in that the type of the state cannot change during the computation.

Normally this is fine, but what if we really wanted to use the mechanics of a state monad to pass some state value that changed type, e.g. some mutually- recursive tree structure we would like to traverse?:

data EvenBranch = EBranch OddBranch Int OddBranch

data OddBranch = OBranch EvenBranch EvenBranch
    | Nil

Well it turns out its not only possible, but relatively simple to use the existing MonadState types with something like a dynamically-typed state, thanks to a couple really fascinating standard Haskell libraries:

Introducing Typeable and Dynamic

The first piece of the puzzle is the Data.Typeable library. The library provides polymorphic functions for extracting type information (in the form of a data type called a TypeRep) from any object in the Typeable type class.

Try this out:

Prelude Data.Dynamic> :t typeOf 
typeOf :: (Typeable a) => a -> TypeRep
Prelude Data.Dynamic> typeOf 1 
Integer 
Prelude Data.Dynamic> typeOf 1 == typeOf 'a' 
False 
Prelude Data.Dynamic> typeOf 1 == typeOf 2 
True

The magic of bringing bits of the abstract type system into the world of data is known as “reification”.

Typeable also provides a method of “casting” any Typeable value as a fixed type, in the Maybe monad. Observe:

Prelude Data.Dynamic> cast 'a' :: Maybe Int 
Nothing 
Prelude Data.Dynamic> cast 'a' :: Maybe Char 
Just 'a'

By using cast the programmer is essentially saying “Compiler, I take it upon myself to deal with any runtime type errors in my code. Take the afternoon off.”

The other component is Data.Dynamic, which uses Typeable (and in fact exports the entire Typeable module) to create a new data type called Dynamic. Thanks goes to Luke Palmer for pointing out the existence of this library to me.

This Dynamic type is like a magic bag for Typeable values:

Prelude Data.Dynamic> :t toDyn  
toDyn :: (Typeable a) => a -> Dynamic
Prelude Data.Dynamic> :t fromDynamic  
fromDynamic :: (Typeable a) => Dynamic -> Maybe a 
Prelude Data.Dynamic> fromDynamic (toDyn 'c') :: Maybe Char 
Just 'c'

So by encapsulating an arbitrary Typeable value in a Dynamic, it becomes a monomorphic value that we can use in lists, functions, etc.

A final interesting thing to note is that the existence of Typeable and Dynamic do not in any way change haskell’s static typing. For example, if the compiler cannot infer the type of a value that we cast it will complain of the ambiguity:

Prelude Data.Dynamic> cast 'a' :: (Typeable a)=>Maybe a
        Ambiguous type variable `a' in the constraint:
          `Typeable a'
            arising from an expression type signature at :1:0-32
        Probable fix: add a type signature that fixes these type variable(s)

Furthermore the compiler will not allow you to do something fancy like define a function that says “if the value is an Int pass it to foo, if it is a String, pass it to bar, else return Nothing”.

Defining the Dynamic State Monad

Okay, now let’s get to the fun part and define our new state monad with dynamic state.

module DynamicState
    where

import Control.Monad.State
import Data.Dynamic

type DynamicState a = StateT Dynamic Maybe a

As you can see our new monad is simply the StateT monad transformer, where the state type is a Dynamic (meaning we’ll be able to marshall in and out whatever state types we like), and the inner monad is Maybe for catching any type threading errors.

-- Dynamic 'get' and 'put' functions:  
putD :: (Typeable a)=> a -> DynamicState ()
putD = put . toDyn

getD :: (Typeable a)=> DynamicState a
getD = get >>= lift . fromDynamic

Above we define our special getter and setter functions. You can see that they are both very simple: putD simply takes a Typeable state value, converts it to a Dynamic and pushes it; getD gets the Dynamic state, casts it into our requested type, and lifts this Maybe value back into the StateT transformer.

That’s about all there is to it, but a couple more functions are useful if we want to completely hide Dynamic from the user:

-- Compiler will complain if we ignore the returned final state since  
-- its type is ambiguous:  
runDState :: (Typeable s, Typeable s')=> DynamicState a -> s -> Maybe (a, s')
runDState c = (liftTypState =<<) . runStateT c . toDyn
    where liftTypState (a,s) = fmap ((,)a) $ fromDynamic s

-- ...instead, use this if we want to discard the final state:  
evalDState :: (Typeable s)=> DynamicState a -> s -> Maybe a
evalDState c = evalStateT c . toDyn

Testing it Out

Let’s use the example we presented above (passing a mutually-recursive data type as state) to demonstrate our new state monad.

To begin with, we must make our EvenBranch/OddBranch type an instance of the Typeable class. This is really easy in GHC with the DeriveDataTypeable extension. Just add this to the top of the file:

{-# LANGUAGE DeriveDataTypeable #-}

…and add “deriving Typeable” to the data declarations for those two types.

Now let’s see an example of a correct computation in the DynamicState monad (I’m sorry this is such a bad example, but I hope it demonstrates the point):

-- a non-sensical and contrived example:  
traverseGood :: DynamicState String
traverseGood = do
    -- we "get" a state value of type EvenBranch  
    (EBranch l i r) <- getD
    -- ...and here we "put" a different type (OddBranch)!  
    if i == 2 then putD r
              else putD l
    (OBranch l' _) <- getD
    -- ...and the state type changes back once more:  
    putD l'
    (EBranch _ i' _) <- getD
    if i' == 3 then return "found 3 in tree"
               else return "3 not found"

Now we can define a little helper function to test our computation and a test EvenBranch tree we can pass as state:

tree = EBranch Nil 2 (OBranch (EBranch Nil 3 Nil) (EBranch Nil 4 Nil))

test :: DynamicState String -> Maybe String
test c = evalDState c tree

And testing it out with GHCi:

*DynamicState> test traverseGood
 Just "found 3 in tree"

Cool! It seems like we got the threading of the state types correct and the algorithm did… what we wanted it to do.

Now let’s intentionally screw up the state, in a slight variation to the code above:

-- same as above but with a programmer error:  
traverseBad :: DynamicState String
traverseBad = do
    (EBranch l i r) <- getD
    if i == 2 then putD r
              else putD l
    -- Oops! This is not the type of our state at this point!:  
    (EBranch l' _ _) <- getD
    putD l'
    (EBranch _ i' _) <- getD
    if i' == 3 then return "found 3 in tree"
               else return "3 not found"

And testing running this computation:

*DynamicState> test traverseBad
Nothing

This time a casting error was caught in our inner Maybe monad and running the computation simply returned Nothing.

Philosophical Conclusion

The existence of Data.Dynamic allows for library designers to implement some pretty cool things that wouldn’t normally be possible in a statically-typed language. But I wonder to what extent its use is not simply “passing the buck” so to speak.

In other words to what extent can a haskell programmer really handle potential type failure at runtime? If Nothings raised by cast et al. failing eventually simply percolate up into an error message thrown in the user’s face, then Typeable seems not much more than a shim to work around haskell’s type system.

Comments