了解Monadic Fibonacci

时间:2016-11-11 17:26:21

标签: haskell monads state-monad

我正在学习haskell和学习monad。我已经观看并阅读了各种教程并为状态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. 内在的第一个陈述内容是什么,即(a,b)<-get导致什么。对于某些具体示例,ab的值是多少。
  2. 你为什么要在这里使用州monad?

3 个答案:

答案 0 :(得分:4)

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

get的类型为MonadState s m => m s所以内部do

(a, b) <- get

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

因此,州将是:

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

外部

(a, b) <- get
return a

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

答案 1 :(得分:3)

首先让我们清楚使用的Fibonacci算法。我们的想法是从元组(0, 1)开始,然后将下一个作为(1, 0 + 1),将下一个作为(1, 1 + 1)(2, 2 + 1)(3, 3 + 2),依此类推。通常,步骤为\(a, b) -> (b, a + b)。你可以看到这些元组中的斐波那契数字。

  

内在的第一个陈述内容是什么,即什么   (a,b)&lt; -get result into?

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

y <- x不是一个完整的表达方式。它类似于x >>= \y ->

y <- x
m

是完整的表达式,等同于x >>= \y -> m。不属于n格式的y <- n行等同于_ <- n(不包括let行,也可能是我忘记的其他行。)

使用这个我们可以desugar do-notation。

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

现在只是了解>>=returngetput等等。

State实际上只是s -> (s, a)类型的函数。它们处于初始状态并产生下一个状态加上一些其他值。

m >>= n a.k.a。&#34; bind&#34;类型为Monad m => m a -> (a -> m b) -> m b。然后,如果我们的Monad是State s,则与以下内容相同:

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

a返回的m必须传递给n。我们还能猜到什么?我们期望状态也会传递,因此m返回的状态也必须传递给n。函数m >>= n必须返回n返回的状态和值。然后我们知道如何实现bind:

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

return :: Monad m => a -> m a,等同于return :: a -> s -> (s, a)

return = flip (,)

get :: State s s相当于get :: s -> (s, s)

get = join (,)

put :: s -> State s ()put :: s -> s -> (s, ())

put s _ = (s, ())

evalState :: s -> State s a -> aevalState :: s -> (s -> (s, a)) -> 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 -> get >>= put . f。因此:

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

然后是部分:

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

任何时候我们都不关心以前的结果,我们可以使用>>

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

这是:

>> get >>= return . fst

m >>= 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 (+)))
  

为什么要在这里使用州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)

答案 2 :(得分:2)

因此,文档说明了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