0

我在 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

这是非常好的代码,它将阶乘实现为命令式函数。但是 replicateM_ 不能完全模拟真实 for 循环的使用。所以我尝试使用延续来创建一些东西,但我失败了这是我的代码:

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

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

4

1 回答 1

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)

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

 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'

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

概括

当你想直接在haskell中做“循环”时,使用尾递归。但实际上,尝试使用像foldand之类的组合器map,它们就像是专门的小循环,GHC 非常擅长优化它们。绝对不要使用IORefs,尝试像 C 一样编写 Haskell 只会损害您的性能、可读性,并且每个人都会感到难过。

于 2013-10-05T18:52:16.380 回答