我有以下代码:
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
创建和初始化变量,使用带有readIORef
和writeIORef
的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
这是我尝试编写阶乘函数。这显然是行不通的。任何帮助将不胜感激。
答案 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
始终读取,写入更改的值。
为了使(希望)位更具可读性,我将test
和body
的{{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
我在这里做的是:
modifyIORef
减少已配对readIORef
/ writeIORef
来电的数量。fmap
减少测试IORef
内容的辅助功能的需要。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 ()
......