2012-09-15 7 views
9

私はハスケルの背後にある数学の基礎を読んでいます。関数の状態を保存するためにクロージャを使う方法について学びました。ハスケルでクロージャーはどのように機能しますか?

ハスケルがクロージャを許可するのかどうか、そして純粋な関数ではないので、どのように動作するのだろうと思いましたか?

関数がクローズオーバー状態を変更すると、同じ入力に異なる出力を与えることができます。

ハスケルでこれはどのように問題ありませんか?最初に値を割り当てた後に変数を再割り当てすることはできないからですか?

+6

単純に、閉鎖状態やその他の変更はできません。 – is7s

+1

類似の質問:http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional- programming-paradigm/ – amindfv

+0

@ is7sですが、アトミックでないデータの場合は、コール間でさらにインスタンス化される可能性があります。 –

答えて

8

クロージャは、関数に追加の変数を追加するだけなので、「通常の」変数よりも何もできることはありません。つまり、確かに状態を変更しないでください。

はもっと読む:あなたが実際にあなたが考えるかもしれない方法をHaskellでクロージャをシミュレートすることはできませんが、 Closures (in Haskell)

+0

初心者の直感的な言葉でこの答えを受け入れていただきありがとうございます – nidoran

+0

haskellは閉じられた変数を値または参照と見なしますか? – CMCDragonkai

+0

純粋なハスケルの世界であると考えると、混乱を招く可能性があるので、まず「リファレンス」と言ったときの意味を具体的に説明しなければならないと思います。 – Bartosz

10

data Closure i o = Respond (i -> (o, Closure i o)) 

これは各「ステップ」のタイプoの応答を計算するために使用されるタイプiの値をとる型を定義:まず、私は、閉鎖型を定義します。

それでは、すなわち、整数で空の入力と回答を受け入れ、「閉鎖」を定義してみましょう:

incrementer :: Closure() Int 

このクロージャの動作が要求にリクエストごとに異なります。私はそれをシンプルに保つ、それが最初の応答に0で応答して、それぞれの連続した要求に対するその応答をインクリメントするようにそれを作ってあげる:

incrementer = go 0 where 
    go n = Respond $ \() -> (n, go (n + 1)) 

私たちは、その後、繰り返しの結果が得閉鎖、およびAを照会することができます新しい閉鎖:

newtype State s a = State { runState :: s -> (a, s) } 

:上記のタイプの後半はStateモナドであるHaskellでは一般的なパターンを、似ていること

query :: i -> Closure i o -> (o, Closure i o) 
query i (Respond f) = f i 

お知らせControl.Monad.Stateからインポートできます。さんはそれを私たちの閉鎖を渡し、見てみましょう

someQuery :: State (Closure() Int) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

query :: i -> State (Closure i o) o 
query i = state $ \(Respond f) -> f i 

を...そして今、私たちはStateモナドを使って任意の閉鎖を照会するための汎用的な方法があります。だから我々はこのStateモナドでqueryをラップすることができます何が起こる:

>>> evalState someQuery incrementer 
(0, 1) 

はのは、いくつかの任意のパターンを返す別のクロージャを書いてみましょう:

weirdClosure :: Closure() Int 
weirdClosure = Respond (\() -> (42, Respond (\() -> (666, weirdClosure)))) 

...それをテストしてください:

>>> evalState someQuery weirdClosure 
(42, 666) 

今、手でクローズを書くのはかなり面倒です。クロージャを書くのにdoの表記法を使うことができればいいのではないでしょうか?まあ、できます! Closure i oため

data Closure i o r = Done r | Respond (i -> (o, Closure i o r)) 

は、今、私たちは(Control.Monadから)Monadインスタンスを定義することができます:私たちは私たちの閉鎖型への1つの変更をしなければならない

instance Monad (Closure i o) where 
    return = Done 
    (Done r) >>= f = f r 
    (Respond k) >>= f = Respond $ \i -> let (o, c) = k i in (o, c >>= f) 

をそして、我々はに対応して便利な機能を書くことができます

:我々はすべて私たちの古いクロージャを書き換えるために使用することができます

answer :: (i -> o) -> Closure i o() 
answer f = Respond $ \i -> (f i, Done()) 

...:単一の要求にサービスを提供

incrementer :: Closure() Int() 
incrementer = forM_ [1..] $ \n -> answer (\() -> n) 

weirdClosure :: Closure() Int r 
weirdClosure = forever $ do 
    answer (\() -> 42) 
    answer (\() -> 666) 

今、私たちは私たちに問い合わせ機能を変更します。

query :: i -> StateT (Closure i o r) (Either r) o 
query i = StateT $ \x -> case x of 
    Respond f -> Right (f i) 
    Done r -> Left r 

...クエリを記述するためにそれを使用します。

someQuery :: StateT (Closure() Int()) (Either()) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

は今それをテスト!

>>> evalStateT someQuery incrementer 
Right (1, 2) 
>>> evalStateT someQuery weirdClosure 
Right (42, 666) 
>>> evalStateT someQuery (return()) 
Left() 

しかし、私はまだ本当にエレガントなアプローチであることは考えていないので、私は臆面もなく書き込み閉鎖との一般的な多くの、より構造化された方法として私pipesに私のProxyタイプを差し込んで締結するつもりだ彼ら消費者。 Serverタイプは一般化されたクロージャを表し、Clientはクロージャの一般化された消費者を表す。

+1

お返事ありがとうございますが、これのほとんどは今のところ私の頭の上です。私がもっと学ぶときこれに戻ります – nidoran

1

他人が言っていたように、Haskellはクロージャの "状態"を変更することを許可していません。これにより、機能の純度を損なう可能性のあることを行うことができなくなります。

+0

それは、関数によって閉じられた変数を宣言し、その関数の外でその変数を後で修正してその関数を実行すると、その関数は私の突然変異を無視します私は閉じた変数を変更しなかったときと同じ出力を返すのですか? – CMCDragonkai

+1

Haskellでは、後で変数を "変更"することはできません。 (またはこれまで。) – MathematicalOrchid

関連する問題