使用Haskell将factorial写为命令函数

Dra*_*gno 0 continuations haskell 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
Run Code Online (Sandbox Code Playgroud)

这是非常好的代码,它将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
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*zer 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)
Run Code Online (Sandbox Code Playgroud)

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

 callCC (\c -> ???)
Run Code Online (Sandbox Code Playgroud)

想想我们???为了循环而必须投入的东西.不知何故,我们想要callCC再次运行,如果它返回一定的值,否则只是继续我们的快乐方式.

但我们投入的任何东西都???无法callCC再次运行!无论我们做什么,它都会返回一个值.所以相反,我们需要做一些事情callCC

 let (continue, val) = callCC (someFunc val)
 in if continue
    then callCallCCAgain val
    else val
Run Code Online (Sandbox Code Playgroud)

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

 loop val = let (continue, val') = doBody val
            in if continue
               then loop val'
               else val'
Run Code Online (Sandbox Code Playgroud)

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

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

摘要

如果要在haskell中直接执行"循环",请使用尾递归.但实际上,尽量使用组合程序像foldmap,他们就像小专业化环路和GHC是在优化他们太棒了.并且绝对不要使用IORefs,试图对Haskell进行编程就好像C会损害你的性能,可读性,每个人都会感到难过.