使用命令式编程的因子

时间:2015-10-21 15:43:43

标签: haskell imperative-programming

我有以下代码:

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

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

2 个答案:

答案 0 :(得分:3)

我想也许是时候给你一些有用的版本了:

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)

如您所见,我使用循环引用i累加器引用acc始终读取,写入更改的值。

为了使(希望)更具可读性,我将testbody的{​​{1}}提取到while和{ {1}}。

当然有更简单的方法(lessOrEqualN),但我想你必须使用它们。

PS:你玩了一下 - 也许你想以不同的方式处理负值或者

这可能会更清晰(将 mutables 放入同一个参考中):

step

答案 1 :(得分:0)

我认为卡斯滕的回答可以像这样清晰一点:

{-# 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
  4. 但坦率地说,我认为你的导师坚持你使用这个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 => Int -> m a -> m () ......