【问题标题】:Factorial using imperative-style programming使用命令式编程的阶乘
【发布时间】:2016-01-20 16:55:46
【问题描述】:

我有以下代码:

while :: IO Bool -> IO () -> IO ()
while test body =
  do b <- test
     if b
       then do {body ; while test body}  -- same-line syntax for do
       else return ()

我需要使用命令式编程来实现阶乘函数。我要做的是使用newIORef 创建和初始化变量,使用readIORefwriteIORef 的while 循环修改它们的值,然后让IO 操作返回由输入n 和最终结果。

这是我到目前为止所做的:

fact :: Integer -> IO (Integer, Integer)
fact n = do r <- newIORef n --initialize variable
            while
              (do {v <- readIORef n; n})
              (do {v <- readIORef r; writeIORef (...)) --modify the value (?)
            readIORef r

这是我编写阶乘函数的尝试。这显然是行不通的。任何帮助将不胜感激。

【问题讨论】:

  • 真的势在必行吗? ...哇,你有一个顽皮的老师......无论如何,我认为如果你添加另一个 variable 对你来说更容易 - 使用两个:一个 accumulator 你将结果和一个 index 您最多计数到 n(使用 while 模拟 for 循环)
  • @Carsten 实际上,我发现这是一个很好的练习,可以检查是否有人知道如何使用IORefs 并理解IO。当然,生成的代码预计很难阅读,而且非常单一。这绝对不是关于优雅的练习;-)
  • 嘿任何能帮助你学习的东西...... ;)

标签: haskell imperative-programming


【解决方案1】:

我想也许是时候给你一些工作版本了:

fact :: Integer -> IO (Integer, Integer)
fact n = do
  i <- newIORef 1
  acc <- newIORef 1
  while (lessOrEqualN i) (step i acc)
  acc' <- readIORef acc
  return $ (n, acc')
  where
     lessOrEqualN iRef = do
       i' <- readIORef iRef
       return $ i' <= n
     step iRef accRef = do
       i' <- readIORef iRef
       acc' <- readIORef accRef
       writeIORef accRef (acc' * i')
       writeIORef iRef (i'+1)

如您所见,我使用了 loop 引用 iaccumulator 引用 acc 始终读取、写入变化的值。

为了让这个(希望)更具可读性,我将whiletestbody 提取到lessOrEqualNstep 中。


当然有更简单的方法可以做到这一点 (modifyIORef),但我想你必须使用这些。


PS:你玩了一下——也许你想以不同的方式处理负值或其他什么


这可能更简洁一些(将两个 mutables 放入同一个 ref):

fact :: Integer -> IO (Integer, Integer)
fact n = do
  ref <- newIORef (1,1)
  while (lessOrEqualN ref) (step ref)
  (_,acc) <- readIORef ref
  return $ (n, acc)
  where
     lessOrEqualN ref = do
       (i,_) <- readIORef ref
       return $ i <= n
     step ref = do
       (i,acc) <- readIORef ref
       writeIORef ref (i+1, acc * i)

【讨论】:

  • 而答案当然是“与普通版本基本相同,但无故复杂得多”。
  • @ReinHenrichs ?现在我真的不知道你在说什么 - 但我觉得我做错了什么!(?)
  • 不,您的解决方案似乎很好(考虑到限制),只是这是一个荒谬的问题陈述。我不明白为什么有人会认为这是一个很好的练习。
  • 所以这是必不可少的 Haskell 代码......这让我很高兴我切换到 Haskell。
  • 我不是!我只是想指出 any 答案必然是令人费解的,因为该问题要求一个令人费解的答案。它基本上是“编写一个阶乘函数,但无缘无故地使用 IORefs 添加了很多间接”。
【解决方案2】:

我认为 Carsten 的答案可以像这样更简洁:

{-# LANGUAGE TupleSections #-}

import Control.Monad
import Data.IORef

fact :: Integer -> IO (Integer, Integer)
fact n = do
  counter <- newIORef 1
  result <- newIORef 1
  while (fmap (<=n) (readIORef counter)) $ do
    i <- postIncrement counter
    modifyIORef result (*i)
  fmap (n,) (readIORef result)

while :: IO Bool -> IO () -> IO ()
while test body =
  do b <- test
     if b
       then do {body ; while test body}  -- same-line syntax for do
       else return ()

postIncrement :: Enum a => IORef a -> IO a
postIncrement ref = do
  result <- readIORef ref
  modifyIORef ref succ
  return result

我在这里做的是:

  1. 使用modifyIORef 减少配对readIORef/writeIORef 调用的数量。
  2. 使用fmap 减少对辅助函数的需求来测试IORef 的内容。
  3. 编写一个通用的、可重用的postIncrement 函数并使用它进一步缩短fact

但坦率地说,我认为你的导师坚持让你使用这个while 函数有点傻。它不适合干净的代码。如果有人告诉我用IORef 编写命令式阶乘,我会先写这个,只需使用库中的forM_ 循环:

factorial :: Integer -> IO (Integer, Integer)
factorial n = do
  result <- newIORef 1
  forM_ [2..n] $ \i -> do
    modifyIORef result (*i)
  fmap (n,) (readIORef result)

那是因为我太笨了,无法马上记住replicateM_ :: Monad m =&gt; Int -&gt; m a -&gt; m ()...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-02
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-22
    • 1970-01-01
    相关资源
    最近更新 更多