【问题标题】:Functional Equivalent of State Design Pattern状态设计模式的功能等价物
【发布时间】:2011-09-13 01:38:40
【问题描述】:

状态设计模式的函数式编程等价物是什么?或者更具体地说,State 设计模式的this Wikipedia example 将如何转化为 FP?

【问题讨论】:

    标签: c# oop design-patterns haskell functional-programming


    【解决方案1】:

    一种编码:

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

    【讨论】:

    • P.S.您还需要捕获计数器切换语义。
    • 对于它的价值,我想说这种编码风格只适用于封装抽象行为的真正的 OO 惯用类。据称 OO 代码中令人沮丧的“类”数量更接近于唐使用的风格的笨拙翻译,因此受益于被转回他们最初想要的样子......
    【解决方案2】:

    此模式是使用State 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
    

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

    【讨论】:

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

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

    【讨论】:

    • FP 确实有状态。没听说过单子?甚至还有一种叫做函数式反应式编程的东西来捕捉随时间变化的状态。
    • 我不认为“有”是连接“函数式编程”和“状态”的正确词。我喜欢将其视为“模型”——我们可以通过构建状态含义的功能模型来进行有状态计算。
    • @Todd monads 是完全纯粹的。您可以在其中携带任何类型的状态。还有STM,它提供了可修改局部变量的概念。在您可以证明的意义上,即使 STM 也是纯粹的,以指定方式使用 STM 的函数是引用透明的。不过,State 可能不是最流行的成语。
    • @FUZxxl,我想你想的是ST,而不是STM
    • @luqui:我认为“有”是一个可行的词,实际上,粗略的意思是“可以使用和操作”。因此,纯语言具有状态的概念,而非纯语言仅具有内在状态。因此,Haskell 拥有比任何命令式语言更多的工具来操作命令式计算,这似乎是一个悖论。
    【解决方案4】:

    也许将 State monad 与自定义修饰符和访问器结合使用?

    【讨论】:

    • 同意,对我来说也像是 State monad 的特殊用途。
    猜你喜欢
    • 2014-02-25
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 2016-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-27
    相关资源
    最近更新 更多