【问题标题】:Understanding Monadic Fibonacci理解一元斐波那契
【发布时间】:2017-08-07 20:43:25
【问题描述】:

我正在学习 haskell 和学习单子。我已经观看并阅读了各种教程,并为 state monad 编写了一些简单的示例,但是我无法理解以下代码(取自 Haskell Wiki):

import Control.Monad.State
fib n = flip evalState (0,1) $ do
  forM [0..(n-1)] $ \_ -> do
    (a,b) <- get
    put (b,a+b)
  (a,b) <- get
  return a

我的问题归结为以下几点:

  1. inner do 的第一条语句中发生了什么,即(a,b)&lt;-get 的结果是什么。对于一些具体示例,ab 的值是什么。
  2. 为什么要在这里使用 state monad?

【问题讨论】:

  • 就 2) 而言,您...不会,在实际代码中,这只是一个玩具示例。

标签: haskell monads state-monad


【解决方案1】:

在此示例中,状态是包含序列中生成的前两个数字的对。这最初是 (0, 1) 提供给 evalState

get 的类型是 MonadState s m =&gt; m s 所以在内部 do 块中

(a, b) <- get

获取状态对并将ab 分别绑定到第一个和第二个元素。然后在以下put 中更新状态。

因此状态将是:

(0, 1), (1, 1), (1, 2), (3, 2), (3, 5), ...

外层

(a, b) <- get
return a

解压最终状态值并返回第一个元素。

【讨论】:

    【解决方案2】:

    首先让我们弄清楚所使用的斐波那契算法。这个想法是从元组(0, 1)开始,然后找到下一个为(1, 0 + 1),下一个为(1, 1 + 1)(2, 2 + 1)(3, 3 + 2),等等。一般步长为\(a, b) -&gt; (b, a + b)。您可以看到在这些元组中是斐波那契数。

    inner do 的第一条语句里面发生了什么,即什么 (a,b)

    Haskell 没有语句,只有表达式。

    y &lt;- x 不是一个完整的表达式。类似于x &gt;&gt;= \y -&gt;

    y <- x
    m
    

    是一个完整的表达式,相当于x &gt;&gt;= \y -&gt; m。不是y &lt;- n 形式的行n 等效于_ &lt;- n(不包括let 行,也许还有一些我忘记了)。

    使用它,我们可以对 do-notation 进行脱糖。

    fib n =
      flip evalState (0, 1)
      ( forM
          [0..(n-1)]
          (\_ -> get >>= (\(a, b) -> put (b, a + b)))
        >>= (\_ -> get >>= (\(a, b) -> return a)))
      )
    

    现在只需要了解&gt;&gt;=returngetput 等等。

    状态实际上只是s -&gt; (s, a) 类型的函数。它们采用初始状态并产生下一个状态以及一些其他值。

    m &gt;&gt;= n a.k.a. "bind" 的类型为 Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b。那么,如果我们的 Monad 是State s,则等同于:

    m >>= n ::
         (     s -> (s, a))
      -> (a -> s -> (s, b))
      -> (     s -> (s, b))
    

    m 返回的a 必须传递给n。我们还能猜到什么?我们希望状态也能传递,所以m 返回的状态也必须传递给n。函数m &gt;&gt;= n 必须返回n 返回的状态和值。然后我们知道如何实现绑定:

    m >>= n = uncurry (flip n) . m
    

    return :: Monad m =&gt; a -&gt; m a 相当于return :: a -&gt; s -&gt; (s, a):

    return = flip (,)
    

    get :: State s s 等价于get :: s -&gt; (s, s)

    get = join (,)
    

    put :: s -&gt; State s ()put :: s -&gt; s -&gt; (s, ())

    put s _ = (s, ())
    

    evalState :: s -&gt; State s a -&gt; aevalState :: s -&gt; (s -&gt; (s, a)) -&gt; a:

    evalState s f = snd (f s)
    

    您可以展开所有定义并准确查看示例中发生的情况。只是直觉就足够了。

    forM
      [0..(n-1)]
      (\_ -> get >>= (\(a, b) -> put (b, a + b)))
    

    我们不关心数字 0n - 1 所以第一个参数被删除。 get 检索当前状态,然后 put 写入新状态。我们这样做n 次。

    >>= (\_ -> get >>= (\(a, b) -> return a)))
    

    我们不关心累加值(即单位),因此第一个参数被删除。然后我们获取当前状态并仅投影该对的第一个元素。这是我们正在寻找的最终答案。

    flip evalState (0, 1) …
    

    最后我们从初始状态(0, 1)开始运行。

    我们可以对此实现进行一些清理。首先,我们不关心[0..(n-1)] 的范围,我们只关心重复一个动作n 次。更直接的方法如下:

    replicateM n (get >>= \(a, b) -> put (b, a + b))
    

    结果是未使用的单元列表,因此更有效的版本是:

    replicateM_ n (get >>= \(a, b) -> put (b, a + b))
    

    已经有一个用于get 后跟put 的通用模式的函数,名为modify,定义为\f -&gt; get &gt;&gt;= put . f。因此:

    replicateM_ n (modify (\(a, b) -> (b, a + b)))
    

    然后是部分:

    >>= (\_ -> get >>= (\(a, b) -> return a)))
    

    任何时候我们不关心之前的结果,我们都可以使用&gt;&gt;

    >> get >>= (\(a, b) -> return a))
    

    这是:

    >> get >>= return . fst
    

    m &gt;&gt;= return . f 简化为fmap f m

    >> fmap fst get
    

    现在我们总共有:

    fib n =
      evalState
      (  replicateM_ n (modify (\(a, b) -> (b, a + b)))
      >> fmap fst get
      )
      (0, 1)
    

    为了比较,我们也可以使用:

    fib n =
      fst
      ( evalState
        (  replicateM_ n (modify (\(a, b) -> (b, a + b)))
        >> get
        )
        (0, 1)
      )
    

    然后因为我很傻:

    fib =
      fst
      . flip evalState (0, 1)
      . (>> get)
      . flip replicateM_ (modify (snd &&& uncurry (+)))
    

    你为什么要在这里使用 state monad?

    你不会的。这很清楚,因为我们只使用状态值;另一个值始终是单位并被丢弃。换句话说,我们只需要在开始时n(即要找到哪个斐波那契数),然后我们只需要累积的元组。

    有时您想拥有一串像h . g . f 这样的组合,但您想发送两个参数而不是一个。那是State 可能适用的时候。

    如果一些函数读取和一些写入状态(第二个参数),或者两者都做,那么State 符合要求。如果只有读者,则使用Reader,如果只有作者,则使用Writer

    我们可以修改示例以更好地利用 State Monad。我会让元组消失!

    fib =
      flip evalState 0
      . foldr (=<<) (return 1)
      . flip replicate (\x -> get >>= \y -> put x $> x + y)
    

    【讨论】:

      【解决方案3】:

      因此文档状态:get :: m s -- Return the state from the internals of the monad(请参阅here)。

      但我记得很清楚,当我试图将头绕在 State Monad 周围时,这对我没有多大帮助。

      我只能建议在 ghci 中使用 :i:t 并测试不同的子表达式。只是为了感受一下。有点像这样:

       import Control.Monad.State.Lazy 
      
       runState (get) 0
       runState (get >>= \x -> put (x+1)) 0
       :t return 1 :: State Int Int
       runState (return 1) 0
       runState (return 1 >>= \x -> (get >>= \y -> return (x+y))) 0 
      
       -- Keeping a pair of (predecessor/current) in the state:
       let f = (get >>= (\(a,b) -> put (b,a+b))) :: State  (Int, Int) ()
       runState (f >> f >> f >> f >> f >> f) (0,1) 
      
       -- only keeping the predecessor in the state:
       let f x = (get >>= (\y -> put x >> return (x+y))) :: State Int Int
       runState (return 1 >>= f >>= f >>= f >>= f >>= f >>= f) 0 
      

      还可以使用modifyrunStateevalStateexecState

      【讨论】:

        猜你喜欢
        • 2015-01-06
        • 1970-01-01
        • 1970-01-01
        • 2012-12-29
        • 1970-01-01
        • 2017-02-22
        • 2018-03-02
        • 2016-06-27
        相关资源
        最近更新 更多