我在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
你能帮我纠正我的代码吗? 感谢
答案 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中直接执行“循环”,请使用尾递归。但实际上,尝试使用像fold
和map
这样的组合器,它们就像是一些专门的循环,而GHC在优化它们方面非常出色。并且绝对不要使用IORef
,试图对Haskell进行编程,因为它只会损害你的性能,可读性,并且每个人都会感到难过。