7

このコード ( Learn You A Haskellから取得):

main = do   putStr "Hey, "  
            putStr "I'm "  
            putStrLn "Andy!"  

どうやら脱糖する

main =        putStr "Hey, " >>=  
       (\_ -> putStr "I'm "  >>= 
       (\_ -> putStrLn "Andy!"))

これは、私が理解しているように、「StrLn "Andy!" を配置するには、まず putStr "I'm " が必要であり、そのためには、最初に putStr "Hey, "; が必要です」と解釈できます。

私はこの解釈に同意しません。これは、コンパイラが明らかにそうではなく、私を混乱させているため、迷惑です。私が抱えている問題は、ラムダが引数を無視することです。遅延評価中に、この種のことは認識されて短絡するはずではありませんか?

また、確かに、バインディングは IO アクションを返し、その IO アクションがメインになると実行されます。しかし、「Hey, Andy!I'm」と表示されないようにするにはどうすればよいでしょうか? 私はそれがバインドがしていることだと思います。

また、「IO ()」タイプの IO アクションは、ランタイム システムが「Hey, I'm Andy!」を出力できるようにするのに十分な情報をどのように運ぶのでしょうか? IO () は、「Hello World!」を出力するよりも IO () とどう違うのですか? またはファイルに書き込みますか?

モナドのウィキペディアのページから、別のことを考えてみましょう:

砂糖漬けバージョン:

do
  putStrLn "What is your name?"
  name <- getLine
  putStrLn ("Nice to meet you, " ++ name ++ "!")

脱糖バージョン:

putStrLn "What is your name?" >>= 
   (\_ ->
      getLine >>=
         (\name ->
            putStrLn ("Nice to meet you, " ++ name ++ "!")))

似たような話はこちら。

IO の bind の定義を確認するだけで、すべてが明確になると思います。他に大いに役立つのは、プログラムが実際にどのように評価され、副作用が発生する正確な瞬間を特定するかを誰かが手伝ってくれることです。

4

5 に答える 5

10

サイモンペイトンジョーンズによる「厄介なチームへの取り組み」の論文を読んでください。

関連する質問については、を参照してください。

塩の粒で私のものを含むそのような説明をしてください-手を振ることは厳密な査読された論文を置き換えることはできません、そして説明は必然的に過度に単純化されています。

非常に大まかな見方は>>=、リストコンストラクターと見なすことができるということです。

data IO = [Primitive] 

IOサブシステムは、そのリストの値を分解してmain消費します。つまり、「メインis just a list. So you may want to take a look at the definition of Haskell entry point aboveメイン,バインド」はかなり面白くありません。

また、haskellの歴史に関する論文を読んだり、IOサブシステムの以前のバージョンを調べて何が起こっているのかを洞察することもできます。

また、C言語を見ると、コナル・エリオットによる純粋関数型の風刺的な投稿です。

機能的純度の定義は自明ではなく、定義を詳しく説明した論文を覚えていますが、タイトルは覚えていません。

于 2011-11-22T11:21:09.047 に答える
9

実際の Haskell の実装を見るとIO、理解するよりも混乱する可能性があります。ただし、次のIOように定義されていると考えてください (これは、GADT を知っていることを前提としています)。

data IO a where
    Return a :: IO a
    Bind :: IO a -> (a -> IO b) -> IO b
    PutStr :: String -> IO ()
    GetLine :: IO String

instance Monad IO where
    return = Return
    (>>=) = Bind

putStr :: String -> IO ()
putStr = PutStr

getLine :: IO String
getLine = GetLine

したがって、( type の) プログラムを評価するときは、実行後に世界との相互作用がどのように発生するかを記述するIO ()type のデータ構造を構築するだけです。IO ()次に、実行エンジンが C などで記述されていると想像できます。ここですべての効果が発生します。

そう

main = do   putStr "Hey, "  
            putStr "I'm "  
            putStrLn "Andy!"  

と同じです

main = Bind (PutStr "Hey, ") (\ _ -> Bind (PutStr "I'm ") (\ _ -> PutStr "Andy!"))

これらの順序付けは、実行エンジンの動作方法に由来します。

とはいえ、実際にこの方法で実行する Haskell の実装を私は知りません。IO実際の実装は、実世界を表すトークンが渡される状態モナドとして実装する傾向があり(これにより順序付けが保証されます)、プリミティブ likeputStrは単なる C 関数の呼び出しです。

于 2011-11-22T12:07:50.473 に答える
3

IO の bind の定義を確認するだけで、すべてが明確になると思います。

はい、そうするべきです。それは実際には非常に簡単で、正しく覚えていれば次のようになります

newtype IO = IO (RealWorld -> (a, RealWorld))

(IO f) >>= g = ioBind f g
    where
       ioBind :: (RealWorld -> (a, RealWorld)) -> (a -> IO b) -> RealWorld -> (b, RealWorld)
       ioBind f g rw = case f rw of
            (a, rw@RealWorld) -> case g a of
                IO b -> b rw

「トリック」は、すべての IO 値が実際には基本的に関数ですが、それを評価するには type のトークンが必要になることですRealWorld。このような値を提供できるインスタンスは 1 つだけです。つまり、main を実行しているランタイム システム (および、名前を付けてはならない関数) だけです。

于 2011-11-22T14:30:41.463 に答える
1

アクションを機能として考え直すと分かりやすいと思います。バインディングの例 ( do { foo <- getLine ; putStrLn foo ; }) は、直感的に次の関数に似ています。

apply arg func = func (arg)

関数がトランザクションであることを除いて。したがって、正常に完了したfunc(arg)場合にのみ、呼び出しが評価されます。(arg)そうでなければfail、私たちは行動します。

これは通常の関数とは異なります。Haskellは、プログラムを続行する(arg)ために少し時間が必要になるまで、完全に計算するかまったく計算しないかを気にしないからです。func(arg)

于 2011-11-22T11:36:22.087 に答える