使用State monad实现factorial和fibonacci(作为学习练习)

时间:2014-11-13 04:11:35

标签: haskell state monads state-monad

我一直在Mike Vanier's monad tutorial(这很棒),我正在how to use a "State" monad的帖子中进行一些练习。

特别是,他提出了一项练习,其中包括使用factorial monad编写fibonacciState的函数。我试了一下,然后想出了下面的答案。 (我发现do符号很混乱,因此我选择了语法)。

我的两个实现都没有特别看到" Haskell-y"并且,为了不将不良做法内化,我想我会问人们如何实现这些功能(使用state monad)。是否可以更简单地编写此代码(除了切换到do表示法)?我强烈怀疑是这种情况。


我意识到为此目的使用state monad有点不切实际,但这纯粹是一种学习练习 - 双关语肯定是有意的。

也就是说,性能并没有那么差:为了计算100000的阶乘(答案是大约21k位数),unfoldr版本需要~1.2秒(在GHCi中)与〜状态monad版本为1.5秒。

import Control.Monad.State (State, get, put, evalState)
import Data.List (unfoldr)

fibonacci :: Integer -> Integer
fibonacci 0 = 0
fibonacci n = evalState fib_state (1,0,1,n)

fib_state :: State (Integer,Integer,Integer,Integer) Integer
fib_state = get >>=
            \s ->
              let (p1,p2,ctr,n) = s
              in case compare ctr n of
                   LT -> put (p1+p2, p1, ctr+1, n) >> fib_state
                   _  -> return p1

factorial :: Integer -> Integer
factorial n = evalState fact_state (n,1)

fact_state :: State (Integer,Integer) Integer
fact_state = get >>=
             \s -> 
               let (n,f) = s 
               in case n of
                      0 -> return f
                      _ -> put (n-1,f*n) >> fact_state

-------------------------------------------------------------------
--Functions below are used only to test output of functions above

factorial' :: Integer -> Integer
factorial' n = product [1..n]

fibonacci' :: Int -> Integer
fibonacci' 0 = 1
fibonacci' 1 = 1
fibonacci' n =  
  let getFst (a,b,c) = a
  in  getFst
    $ last 
    $ unfoldr (\(p1,p2,cnt) -> 
               if cnt == n
                  then Nothing
                  else Just ((p1,p2,cnt)
                            ,(p1+p2,p1,cnt+1))
              ) (1,1,1) 

2 个答案:

答案 0 :(得分:3)

你的功能似乎比它们需要的要复杂得多,但你有正确的想法。对于阶乘,您需要跟踪的是您乘以的当前数字和到目前为止累积的数字。所以,我们会说State Int Int是一个计算,它对状态中的当前数字进行操作,并返回到目前为止已成倍增加的数字:

fact_state :: State Int Int
fact_state = get >>= \x -> if x <= 1
                           then return 1
                           else (put (x - 1) >> fmap (*x) fact_state)

factorial :: Int -> Int
factorial = evalState fact_state

Prelude Control.Monad.State.Strict Control.Applicative> factorial <$> [1..10]
[1,2,6,24,120,720,5040,40320,362880,3628800]

斐波那契序列相似。你需要保留最后两个数字,以便了解你将要加在一起的内容,以及到目前为止你走了多远:

fibs_state :: State (Int, Int, Int) Int
fibs_state = get >>= \(x1, x2, n) -> if n == 0
                                     then return x1
                                     else (put (x2, x1+x2, n-1) >> fibs_state)

fibonacci n = evalState fibs_state (0, 1, n)

Prelude Control.Monad.State.Strict Control.Applicative> fibonacci <$> [1..10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

答案 1 :(得分:2)

两种风格建议:

        \s ->
          let (p1,p2,ctr,n) = s
          in ...

相当于:

        \(p1,p2,ctr,n) -> ...

case的{​​{1}}语句可以使用fib_state语句撰写:

if