使用Haskell将factorial写为命令函数

时间:2013-10-05 16:42:32

标签: haskell continuations monad-transformers

我在Haskell中编写了以下代码:

import Data.IORef
import Control.Monad
import Control.Monad.Trans.Cont
import Control.Monad.IO.Class
fac n = do
          i<-newIORef 1
          f<-newIORef 1
          replicateM_ n $ do
            ri<-readIORef i
            modifyIORef f (\x->x*ri)
            modifyIORef i (+1)
          readIORef f

这是非常好的代码,它将factorial实现为命令式函数。但是replicateM_无法完全模拟真实for循环的使用。所以我尝试使用continuation创建一些东西,但我在这里失败的是我的代码:

ff = (`runContT` id) $ do
       callCC $ \exit1 -> do
         liftIO $ do
           i<-newIORef 1
           f<-newIORef 1
         callCC $ \exit2 -> do
           liftIO $ do 
             ri<-readIORef i
             modifyIORef (\x->x*ri)
             modifyIORef i (+1)
             rri<-readIORef i
             when (rri<=n) $ exit2(())
         liftIO $ do
           rf<-readIORef f
           return rf

你能帮我纠正我的代码吗? 感谢

1 个答案:

答案 0 :(得分:5)

既然你是Haskell的初学者,而不仅仅是为了学习延续和IORefs是如何工作的,那么你做错了。

编写命令循环的Haskell-y方式是尾调用或折叠。

factorial n = foldl1' (*) [1..n]

factorial' n = go 1 n
   where go accum 0 = accum
         go accum n = go (n-1) (accum * n)

此外,由于Haskell的callCC本质上为您提供了早期回报,因此使用它来模拟循环是行不通的。

 callCC (\c -> ???)

考虑为了循环我们必须为???添加什么。不知何故,我们想要再次运行callCC,如果它返回一定的值,否则只是继续我们的快乐方式。

但是我们放入???的任何内容都无法让callCC再次投放!无论我们做什么,它都会返回一个值。因此,我们需要围绕callCC

做一些事情
 let (continue, val) = callCC (someFunc val)
 in if continue
    then callCallCCAgain val
    else val

这样的事情对吗?但等等,callCallCCAgain是递归!这甚至是尾递归!事实上,callCC没有任何好处

 loop val = let (continue, val') = doBody val
            in if continue
               then loop val'
               else val'

看起来很熟悉?这与上面factorial'的结构相同。

你仍然可以使用IORef和类似monad-loops包的东西,但它总是一场艰苦的战斗,因为Haskell并不是那样写的。

摘要

如果要在haskell中直接执行“循环”,请使用尾递归。但实际上,尝试使用像foldmap这样的组合器,它们就像是一些专门的循环,而GHC在优化它们方面非常出色。并且绝对不要使用IORef,试图对Haskell进行编程,因为它只会损害你的性能,可读性,并且每个人都会感到难过。