7

putStrLn引数を指定して呼び出すと、常にタイプの値が返されますIO ()。私はそれが純粋であることに同意します、私はそれを扱うことができます。しかし、それは参照透過性ですか?私はそう思います。なぜなら、与えられた入力に対して、関数呼び出しをIO ()stdoutで正しい文字列をスローするanに置き換えることができるからです。

だから私は。でかっこいいですが、引数なしで呼び出されputStrLnたとき、それらがタイプであるならば、いくつものものを返すことができます。それは純粋でも参照透過性でもありませんよね?getLineIO String

ばかげた衒学的な質問で、コードの書き方はおそらく変わらないでしょうが、私は本当にこれを完全に釘付けにしたいのです。(IOモナドが物事を正しくシーケンスすることを理解しています。それは私の問題ではありません)

これは私に別の質問を提起します。コンパイラは、入力を受け取らないプログラムを認識するのに十分賢いですか?たとえば、私がコンパイルするとします

main = putStrLn . show $ map (+1) [1..10]

IO ()GHCは、そのプログラムを[2,3,4,5,6,7,8,9,10,11]が出力されるように減らすのに十分賢いですか?それとも、それでもうまくいき、実行時にすべてを評価/実行しますか?入力が不要な任意のプログラムについても同様です。GHCは、プログラム全体が参照透過性であり、その価値に簡単に置き換えることができるという事実を採用していますか?

4

6 に答える 6

7

ここには2つの質問があると思います。

  1. IOは参照透過性ですか
  2. GHCはコンパイル時に任意の式を減らしますか

IOのタイプを見るとRealWorld、データコンストラクターを持たない不思議な値に依存し、各ステートメントを最後(シングルスレッドの世界)に依存させることで、参照透過性をエミュレートしていることが想像できます。の場合、これは...IO Stringのニュータイプラッパーであり、値ではなく関数です。インスタンスなしでRealWorld -> (RealWorld, String)使用すると、これは特に、そして痛々しいほど明白になります。IOMonad

Prelude GHC.Types> :info IO
newtype IO a
  = IO (GHC.Prim.State# GHC.Prim.RealWorld
        -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))

GHCの最適化に関しては、この場合、コンパイル時にリストが文字列に縮小されることはありません。GHC 7.2.1によって生成された最適化されたコードは、リストを遅延生成し、結果にマップ(+1)し、リストを文字列に変換し、最後にコンソールに出力します。あなたの例で読んだのとほぼ同じです。

于 2011-12-05T08:47:33.847 に答える
7

はい、これらのモナディック関数は、置換規則が引き続き適用されるため、純粋に参照透過的です。

Haskellでは、次の2つのプログラムは同等です

main = (puStrLn "17" >> puStrLn "17")
main = let x = putStrLn "17" in (x >> x)

「通常の」言語では、2番目の例は、評価の副作用として1回だけ出力されますxIO()タイプの値が実際には副作用のある計算ではなく、実際には、より大きな計算を構築するためのビルディングブロックとして使用できるような計算の説明であることに気付くと、2つのプログラムが実際に同じである方法が少し明確になります。。

于 2011-12-05T14:17:29.750 に答える
6

getLine :: IO String純粋です。その値は、標準入力から文字列を読み取って返す*IOアクションです。getLine常にこの値に等しくなります。

*より良い言葉がないため、ここでは「リターン」という言葉を使用します。

ウィキペディアでは、参照透過性を次のように定義しています。

式は、プログラムの動作を変更せずにその値に置き換えることができる場合(つまり、同じ効果と同じ入力で出力されるプログラムを生成する場合)、参照透過性があると言われます。

したがって、getLineも参照透過性です。「表現をその価値に置き換える」という目的のために、その「価値」を他の方法で表現する良い方法を考えることはできませんが。

putStrLnまた、「引数を指定して呼び出すと常に返される」などのステートメントには少し注意する必要がありますIO ()IO ()はタイプであり、値ではありません。ごとs :: Stringに、putStrLn sはタイプの値ですIO ()、はい。sしかし、もちろん、この値が何であるかは異なります。

(さらに、それらを除外するunsafeと、すべてが純粋で参照透過性になり、特にそうなりますgetLine。)

于 2011-12-05T09:14:16.020 に答える
4

質問の2番目の部分に答えさせてください(前の質問の最初の部分に答えました)。コンパイラーは、プログラムのセマンティクスを変更しない限り、式に対して自由に実行できます。したがって、特定のコンパイラについて質問する必要があります。ghcですか?いいえ、現在のバージョンではありません。するコンパイラはありますか?はいあります。

于 2011-12-05T12:46:59.577 に答える
3

これが役立つかどうかはわかりませんが(混乱するだけの場合は事前に謝罪します)、MercuryでIOを参照透過性にする方法は、タイプの値ioをすべてのIO実行コードに明示的に渡すことです。タイプの新しい値io

入力ioは、コードが呼び出される直前の「世界の状態」を表します。プログラム外の全世界。ディスクの内容、画面に表示される内容、ユーザーが入力しようとしている内容、ネットワークから受信しようとしている内容、すべて。

出力ioは、コードが呼び出された直後の世界の状態を表します。io入力と出力の違いにはio、そのコードによって行われた世界への変更(および理論的には外部で発生した他のすべて)が含まれます。

ioMercuryのモードシステムは、typeの値が一意であることを保証します。それらは1つしかないため、io2つの異なるIO実行手順に同じ値を渡すことはできません。をプロシージャに渡し、ioそれを役に立たなくしてから、新しいプロシージャを受け取ります。

もちろん、実際の世界の実際の状態は、タイプの値にエンコードさioれていません。実際、ボンネットの下ioは完全に空です!渡される情報はまったくありません!しかし、io値は世界の状態を表しています。

IOモナドの関数は同じことをしていると考えることができます。それらは、追加の暗黙の世界の状態の引数を取り、追加の暗黙の世界の状態の値を返します。IOモナドの実装は、この余分な出力を次の関数に渡すことを処理します。これにより、IOモナドはStateモナドと非常によく似たものになります。getそのモナドでは、引数を取らないように見えても、純粋であることが簡単にわかります。

その理解では、mainは、プログラムが実行される前にワールドの初期状態を受け取り、プログラムが実行された後、プログラム内のすべてのIOコードにスレッド化することによって、ワールドの状態に変換します。

また、自分で最新の値を取得することはできないため、他のコードの途中で独自の小さなIOチェーンを開始する方法はありません。これが純粋さを保証するものです。実際、私たちはどこからともなく独自の状態が湧き出る真新しい世界を持つことはできないからです。

したがってgetLine :: IO String、のようなものと考えることができますgetLine :: World -> (World, String)。それは純粋です。なぜなら、それが呼び出され、毎回異なる受信した異なる文字列を返すためです。 World

IOアクションである値、関数間で受け渡される世界の状態、またはその他のメカニズムについて考えるかどうかにかかわらず、これらの構成はすべて表現的です。内部的には、すべてのIOは不純なコードで実装されています。これが、世界の仕組みだからです。ファイルに書き込むときは、ディスクの状態が変更されています。しかし、これをより高いレベルの抽象化で表すことができるため、別の考え方をすることができます。

例えは、検索ツリーやハッシュテーブル、またはその他の無数の方法でマップを実装できることです。しかし、それを実装した後、マップを使用するコードについて推論するときは、左右のサブツリーやバケットとハッシュについては考えず、マップである抽象化について考えます。

純度と参照透過性を維持する方法でIOを表現できる場合は、この表現を使用して、参照透過性を必要とする任意の推論をコードに適用できます。これにより、IOを実行するプログラムであっても、そのようなコードに適用されるすべての数学が機能します(その多くは、純度が強化された言語の高度なコンパイラーの実装で使用されます)。


そして、2番目の質問についての簡単な補遺。GHCは、理論的にはその入力プログラムを出力だけに減らすことができます。しかし、これは一般的に決定不可能であるため、そうするのがひどく難しいとは思わない。入力を受け取らずに無限のリストを生成し、最後の3つの要素を出力したプログラムを想像してみてください。理論的には、入力に依存しないプログラムはすべて出力に還元できますが、そのためには、コンパイラーはコンパイル時にプログラムを実行するのと同等のことを行う必要があります。したがって、これを完全に行うには、通常、コンパイル時にプログラムが無限ループに陥ることに満足している必要があります。そして、ほとんどすべてのプログラムその入力に依存しているので、これを行おうとしても多くのことは得られません。

入力に依存しないプログラムの部分を特定し、それらを結果に置き換えることで得られるものあります。これは部分評価と呼ばれ、活発な研究トピックですが、非常に難しく、万能の解決策はありません。それを行うには、コンパイラを無限ループに送り込まないプログラムの領域を特定して、それらが何を返すかを理解する必要があります。また、いくつかのコードを削除するかどうかを決定する必要があります。実行時の秒数は、返される数百メガバイトのデータ構造をプログラムバイナリに埋め込むことを意味する場合、十分なメリットがあります。そして、適度に複雑なプログラムをコンパイルするのに何時間もかかることなく、このすべての分析を行う必要があります。

于 2011-12-06T00:00:22.357 に答える
2

質問の2番目の部分について。スーパーコンパイルと呼ばれるものがあり、うまくいけばそのようなものを拾うでしょう。それはまだ研究分野です。

于 2011-12-05T09:50:52.553 に答える