3

我有以下代码:

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,使用带有readIORefand的 while 循环修改它们的值writeIORef,然后让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

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

4

2 回答 2

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,总是读取、写入变化的值。

为了使这个(希望)更具可读性,我提取了in的testand和。bodywhilelessOrEqualNstep


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


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


这可能会更干净一些(将两个可变变量放入同一个引用中):

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)
于 2015-10-21T17:20:54.123 回答
1

我认为 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 => Int -> m a -> m ()...

于 2015-10-22T18:04:25.297 回答