状态设计模式在函数式编程中的等效方式是什么?或者更具体地说,这个维基百科的例子如何转换为函数式编程?
import Control.Monad.Trans.State
import Control.Monad.IO.Class
import Data.Char
data Mode = A | B
type StateM a = StateT (Int, Mode) IO a
write函数是StateM上下文中的一个函数,它的行为根据有状态模式而改变:
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)
运行程序,在状态 A 中最初启动一个有状态的计算
main = flip runStateT (0, A) $ do
writeName "Monday"
writeName "Tuesday"
writeName "Wednesday"
writeName "Thursday"
writeName "Saturday"
writeName "Sunday"
从上面的代码中,main函数的输出为:
monday
TUESDAY
WEDNESDAY
thursday
SATURDAY
SUNDAY
一种编码:
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)
不要被IO
所迷惑,这只是该模式的纯翻译(我们没有使用IORef
来存储状态或任何其他东西)。扩展newtype
,我们可以看到这种类型的含义:
State = String -> IO (String -> IO (String -> IO (String -> ...
它接受一个字符串,进行一些I/O操作,并要求输入另一个字符串等。
这是我最喜欢的OO抽象类模式编码方式:抽象类 -> 类型,子类 -> 该类型的元素。
newtype State
声明取代了抽象writeName
声明及其签名。我们不需要将StateContext
传递给它来分配新状态,而是让它返回新状态。将返回值嵌入IO
中表示新状态允许依赖于I/O。由于在这个例子中技术上并不必要,因此我们可以使用更严格的类型。
newtype State = State { unState :: String -> (State, IO ()) }
我们可以使用一种方法来表达这个计算,但状态序列是固定的,不能依赖于输入。但让我们坚持原始的、更宽松的类型。
至于“测试客户端”:
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" ]
State
Monad?State
单子的一个特定用法。 - Don Stewart我认为在纯函数式编程中没有状态模式的纯函数式等效方法。因为纯函数式编程没有状态和时间的概念。状态模式本质上是关于状态和时间的。但我认为非纯函数式等效方法是存在的,它就是无限惰性求值流。你可以用C# yield来实现它。
ST
而不是STM
。 - luqui
data Mode
),而在维基百科的例子中,声明可以按模块组合。 - luqui