开发者

Functional Equivalent of State Design Pattern

开发者 https://www.devze.com 2023-03-12 19:08 出处:网络
What would be the functional programming equivalent of the State design patter开发者_StackOverflow社区n? Or more concretely, how would this Wikipedia example of State design pattern will translate to

What would be the functional programming equivalent of the State design patter开发者_StackOverflow社区n? 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" ]


Maybe with a State monad combined with custom modifiers and accessors?


I don't think there's pure functional equivalent for state pattern. Because pure functional programming doesn't have the concept of state and time. State pattern is intrinsically about state and time. But I think the non-pure functional equivalent exists, it's infinite lazy evaluated stream. You can implement it with C# yield.

0

精彩评论

暂无评论...
验证码 换一张
取 消