【问题标题】:Write factorial as imperative function using Haskell使用 Haskell 将阶乘写为命令式函数
【发布时间】:2013-10-05 16:42:31
【问题描述】:

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

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

【问题讨论】:

  • 只是为了确保:您是尝试 IORef 和延续的高级用户,还是需要有关如何以命令式样式编写阶乘函数的建议的初学者?
  • 但是...为什么yyyy?
  • @kqr 我是初学者。
  • 你想要来自Control.MonadforM_
  • @Dragno 你被误导了。 replicateM 不会多次复制代码。此外,即使您使用replicateM 的代码也让我想知道,“为什么?”。如果您允许,事情会变得非常简单。

标签: haskell continuations monad-transformers


【解决方案1】:

由于您是 Haskell 的初学者,而不是仅仅为了了解延续和 IORef 的工作原理而这样做,所以您做错了。

编写命令式循环的 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' 结构相同。

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

总结

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

【讨论】:

    猜你喜欢
    • 2017-03-02
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多