问题 状态设计模式的功能等价


什么是函数式编程等同于State设计模式?或者更具体地说,如何 这个维基百科的例子 国家设计模式将转化为FP?


10639
2018-06-11 14:10


起源



答案:


这种模式是使用的一个例子 州monad,一个计算 环境增强了代码与状态。

这是Haskell中的一个实现。

一些助手:

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

请注意,这是一个纯功能解决方案。此程序中没有可变或破坏性的更新。相反,状态monad通过计算线程化所需的模式。


7
2018-06-11 14:42



我不会说这是一个忠实的编码:这里我们必须在一个地方声明所有模式(data Mode),而在维基百科示例中,声明可以模块化组合。 - luqui
这是ML风格语言中封闭数据类型的标准转换。显然,我们可以通过函数或类型类来逐步使用开放数据类型,但我认为它与此问题无关。 - Don Stewart
我很好奇它是什么标准翻译,你的标准是什么意思。 - luqui
我在争论 惯用的 事情是将具有多个变体的对象转换为代数数据类型。 - Don Stewart


答案:


这种模式是使用的一个例子 州monad,一个计算 环境增强了代码与状态。

这是Haskell中的一个实现。

一些助手:

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

请注意,这是一个纯功能解决方案。此程序中没有可变或破坏性的更新。相反,状态monad通过计算线程化所需的模式。


7
2018-06-11 14:42



我不会说这是一个忠实的编码:这里我们必须在一个地方声明所有模式(data Mode),而在维基百科示例中,声明可以模块化组合。 - luqui
这是ML风格语言中封闭数据类型的标准转换。显然,我们可以通过函数或类型类来逐步使用开放数据类型,但我认为它与此问题无关。 - Don Stewart
我很好奇它是什么标准翻译,你的标准是什么意思。 - luqui
我在争论 惯用的 事情是将具有多个变体的对象转换为代数数据类型。 - Don Stewart


一种编码:

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" ]

5
2018-06-11 14:43



附:您还需要捕获计数器切换语义。 - Don Stewart
@Don,哦,对。嗯.... - luqui
对于它的价值,我认为这种编码风格仅适用于封装抽象行为的真正OO惯用类。据称OO代码中令人沮丧的“类”数量更接近于Don使用的风格的笨拙翻译,因此可以从最初变成他们想要的东西中受益... - C. A. McCann


也许有一个 State monad结合自定义修饰符和访问器?


1
2018-06-11 14:20



同意,看起来像是一个特殊的用途 State 我也是monad。 - Don Stewart


我不认为状态模式有纯粹的功能等价物。因为纯函数式编程没有状态和时间的概念。状态模式本质上是关于状态和时间。但我认为非纯函数等价物存在,它是无限懒惰的评估流。您可以使用C#yield实现它。


-3
2018-06-11 14:22



FP确实有状态。从未听说过monads?甚至还有一种叫做功能反应式编程的东西来捕捉时变状态。 - fuz
我不认为“has”是连接“函数式编程”和“状态”的正确词。我喜欢将其视为“模型” - 我们可以通过构建状态的功能模型来进行有状态的计算。 - luqui
@Todd monads是完全纯粹的。你可以在其中携带任何一种状态。还有STM,它提供可修改局部变量的概念。即使STM是纯粹的,你可以证明,以指定方式使用STM的函数是引用透明的。然而,州可能不是最流行的习语。 - fuz
@FUZxxl,我想你在考虑 ST不是 STM。 - luqui
@luqui:我认为“has”是一个可行的词,实际上,粗略地说“可以使用和操纵”。因此,纯语言有 国家的概念而一种不纯的语言只是有 内在状态。因此看似矛盾的是,Haskell拥有比任何命令式语言更多的工具来操纵命令式计算。 - C. A. McCann