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を使用して while ループを使用してそれらの値を変更し、アクションが入力と最終結果からなるペアを返すようにすることです。readIORefwriteIORefIOn

これは私がこれまでに行ったことです:

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常に変化する値を読み取り、書き込みます。

これを (できれば)もう少し読みやすくするために、testと のbodywhilelessOrEqualNに抽出しましたstep


もちろん、これを行うより簡単な方法があります ( modifyIORef) が、それらを使用する必要があると思います。


PS: 少し遊んでみます - 負の値を別の方法で処理したい場合など


これは少しきれいかもしれません(両方のミュータブルを同じ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)
于 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の回数を減らすために使用します。readIORefwriteIORef
  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 に答える