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?
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.
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" ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With