Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional Equivalent of State Design Pattern

What would be the functional programming equivalent of the State design pattern? Or more concretely, how would this Wikipedia example of State design pattern will translate to FP?

like image 708
Rajesh Kutrapalli Avatar asked Jun 11 '11 14:06

Rajesh Kutrapalli


2 Answers

This pattern is an example of the use of the State monad, a computational environment tha augments code with state.

Here's an implementation in Haskell.

Some helpers:

import Control.Monad.Trans.State
import Control.Monad.IO.Class
import Data.Char

The two modes of operation of the program

data Mode = A | B

The type of stateful computations with this mode, augmented with a counter.

type StateM a = StateT (Int, Mode) IO a

The write function, a function in the StateM context, changes its behavior based on the stateful mode:

writeName :: String -> StateM ()
writeName s = do
    (n,mode) <- get
    case mode of
        A -> do liftIO (putStrLn (map toLower s))
                put (0,B)
        B -> do let n' = n + 1
                liftIO (putStrLn (map toUpper s))
                if n' > 1 then put (n', A)
                          else put (n', B)

Run the program, launching a stateful computation initially in state A

main = flip runStateT (0, A) $ do
    writeName "Monday"
    writeName "Tuesday"
    writeName "Wednesday"
    writeName "Thursday"
    writeName "Saturday"
    writeName "Sunday"

From the above code, the output of main is:

monday
TUESDAY
WEDNESDAY
thursday
SATURDAY
SUNDAY

Note that this is a purely functional solution. There is no mutable or destructive updates in this program. Instead, the state monad threads the desired mode through the computation.

like image 172
Don Stewart Avatar answered Sep 19 '22 13:09

Don Stewart


One encoding:

import Data.Char (toUpper, toLower)

newtype State = State { unState :: String -> IO State }

stateA :: State
stateA = State $ \name -> do
    putStrLn (map toLower name)
    return stateB

stateB :: State
stateB = go 2
    where
    go 0 = stateA
    go n = State $ \name -> do
               putStrLn (map toUpper name)
               return $ go (n-1)

Don't be fooled by the IO, this is a pure translation of that pattern (we are not using an IORef to store the state or anything). Expanding the newtype, we see what this type means:

State = String -> IO (String -> IO (String -> IO (String -> ...

It takes a string, does some I/O and asks for another string, etc.

This is my favorite encoding of abstract class patterns in OO: abstract class -> type, subclasses -> elements of that type.

The newtype State declaration takes the place of the abstract writeName declaration and its signature. Instead of passing a StateContext into which we assign a new state, we just have it return the new state. Embedding the return value in IO says that the new state is allowed to depend on I/O. Since that is not technically necessary in this example, we could use the more stringent type

newtype State = State { unState :: String -> (State, IO ()) }

in which we can still express this computation, but the sequence of states is fixed and not allowed to depend on input. But let's stick to the original, more lenient type.

And for the "test client":

runState :: State -> [String] -> IO ()
runState s [] = return ()
runState s (x:xs) = do
    s' <- unState s x
    runState s' xs

testClientState :: IO ()
testClientState = runState stateA
                   [ "Monday"
                   , "Tuesday"
                   , "Wednesday"
                   , "Thursday"
                   , "Saturday"
                   , "Sunday" ]
like image 22
luqui Avatar answered Sep 20 '22 13:09

luqui