関数型プログラミング言語で変数をインクリメントするにはどうすればよいですか?
たとえば、私はやりたい:
main :: IO ()
main = do
let i = 0
i = i + 1
print i
期待される出力:
1
関数型プログラミング言語で変数をインクリメントするにはどうすればよいですか?
たとえば、私はやりたい:
main :: IO ()
main = do
let i = 0
i = i + 1
print i
期待される出力:
1
簡単な方法は、変数名のシャドウイングを導入することです:
main :: IO () -- another way, simpler, specific to monads:
main = do main = do
let i = 0 let i = 0
let j = i i <- return (i+1)
let i = j+1 print i
print i -- because monadic bind is non-recursive
印刷し1
ます。
Haskell では再帰的な定義を行うため、ただ書くだけでlet i = i+1
は機能しません。実際には、Scheme の. の右側にあるは、左側にある を指します。意図されているように、上のレベルではありません。そこで、別の変数 を導入してその方程式を分解します。let
letrec
i
let i = i+1
i
i
j
もう 1 つの簡単な方法は、- 表記<-
でモナド バインドを使用することdo
です。これが可能なのは、モナド バインドが再帰的でないためです。
どちらの場合も、同じ名前で新しい変数を導入することで、古いエンティティを「シャドウイング」します。つまり、アクセスできなくなります。
ここで理解しておくべきことの 1 つは、(Haskell のように) 純粋な (不変の) 値を使用する関数型プログラミングでは、コード内で時間を明示する必要があるということです。
必須の設定では、時間は暗黙的です。vars を「変更」しますが、変更はシーケンシャルです。その varが一瞬前に何であったかを変更することは決してできません — それがこれからどうなるかだけです。
純粋な関数型プログラミングでは、これは明示的に行われます。これが取り得る最も単純な形式の 1 つは、命令型プログラミングで順次変更の記録として値のリストを使用することです。さらに簡単なのは、さまざまな変数をまとめて使用して、さまざまな時点でのエンティティのさまざまな値を表すことです (cf.単一代入および静的単一代入フォーム、または SSA)。
したがって、とにかく実際には変更できないものを「変更」する代わりに、その拡張コピーを作成し、それを古いものの代わりに使用して渡します。
原則として、必要ありません (必要ありません)。ただし、完全性のために。
import Data.IORef
main = do
i <- newIORef 0 -- new IORef i
modifyIORef i (+1) -- increase it by 1
readIORef i >>= print -- print it
ただし、MVar、IORef、STRefなどを使用する必要があるという答えは間違っています。これを行うための純粋に機能的な方法がありますが、この小さくて迅速に書かれた例では、あまり見栄えがよくありません。
import Control.Monad.State
type Lens a b = ((a -> b -> a), (a -> b))
setL = fst
getL = snd
modifyL :: Lens a b -> a -> (b -> b) -> a
modifyL lens x f = setL lens x (f (getL lens x))
lensComp :: Lens b c -> Lens a b -> Lens a c
lensComp (set1, get1) (set2, get2) = -- Compose two lenses
(\s x -> set2 s (set1 (get2 s) x) -- Not needed here
, get1 . get2) -- But added for completeness
(+=) :: (Num b) => Lens a b -> Lens a b -> State a ()
x += y = do
s <- get
put (modifyL x s (+ (getL y s)))
swap :: Lens a b -> Lens a b -> State a ()
swap x y = do
s <- get
let x' = getL x s
let y' = getL y s
put (setL y (setL x s y') x')
nFibs :: Int -> Int
nFibs n = evalState (nFibs_ n) (0,1)
nFibs_ :: Int -> State (Int,Int) Int
nFibs_ 0 = fmap snd get -- The second Int is our result
nFibs_ n = do
x += y -- Add y to x
swap x y -- Swap them
nFibs_ (n-1) -- Repeat
where x = ((\(x,y) x' -> (x', y)), fst)
y = ((\(x,y) y' -> (x, y')), snd)
i=i+1
命令型プログラミングを関数型プログラミングに変換するソリューションはいくつかあります。再帰関数ソリューションは、関数型プログラミングで推奨される方法です。状態を作成することは、ほとんど決してやりたいことではありません。
しばらくすると[1..]
、たとえばインデックスが必要な場合に使用できることがわかりますが、命令的ではなく機能的に考えるには多くの時間と練習が必要です。
i=i+1
破壊的な更新がないため、同一ではないのと同様のことを行う別の方法を次に示します。State モナドの例は単なる説明のためのものであり、おそらく代わりに次のようにしたいことに注意してください[1..]
:
module Count where
import Control.Monad.State
count :: Int -> Int
count c = c+1
count' :: State Int Int
count' = do
c <- get
put (c+1)
return (c+1)
main :: IO ()
main = do
-- purely functional, value-modifying (state-passing) way:
print $ count . count . count . count . count . count $ 0
-- purely functional, State Monad way
print $ (`evalState` 0) $ do {
count' ; count' ; count' ; count' ; count' ; count' }