これが役立つかどうかはわかりませんが(混乱するだけの場合は事前に謝罪します)、MercuryでIOを参照透過性にする方法は、タイプの値io
をすべてのIO実行コードに明示的に渡すことです。タイプの新しい値io
。
入力io
は、コードが呼び出される直前の「世界の状態」を表します。プログラム外の全世界。ディスクの内容、画面に表示される内容、ユーザーが入力しようとしている内容、ネットワークから受信しようとしている内容、すべて。
出力io
は、コードが呼び出された直後の世界の状態を表します。io
入力と出力の違いにはio
、そのコードによって行われた世界への変更(および理論的には外部で発生した他のすべて)が含まれます。
io
Mercuryのモードシステムは、typeの値が一意であることを保証します。それらは1つしかないため、io
2つの異なる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つの要素を出力したプログラムを想像してみてください。理論的には、入力に依存しないプログラムはすべて出力に還元できますが、そのためには、コンパイラーはコンパイル時にプログラムを実行するのと同等のことを行う必要があります。したがって、これを完全に行うには、通常、コンパイル時にプログラムが無限ループに陥ることに満足している必要があります。そして、ほとんどすべてのプログラムはその入力に依存しているので、これを行おうとしても多くのことは得られません。
入力に依存しないプログラムの部分を特定し、それらを結果に置き換えることで得られるものがあります。これは部分評価と呼ばれ、活発な研究トピックですが、非常に難しく、万能の解決策はありません。それを行うには、コンパイラを無限ループに送り込まないプログラムの領域を特定して、それらが何を返すかを理解する必要があります。また、いくつかのコードを削除するかどうかを決定する必要があります。実行時の秒数は、返される数百メガバイトのデータ構造をプログラムバイナリに埋め込むことを意味する場合、十分なメリットがあります。そして、適度に複雑なプログラムをコンパイルするのに何時間もかかることなく、このすべての分析を行う必要があります。