副作用は参照透過性を損なうので、関数型言語のポイントに反しませんか?
4 に答える
副作用をモデル化するために純粋関数型プログラミング言語で使用される2つの手法があります。
1)外部状態を表すワールド型。その型の各値は、型システムによって1回だけ使用されることが保証されています。
このアプローチを使用する言語では、関数print
であり、それぞれread
タイプ(string, world) -> world
とを持っている可能性がありworld -> (string, world)
ます。
それらは次のように使用される可能性があります。
let main w =
let w1 = print ("What's your name?", w) in
let (name, w2) = read w1 in
let w3 = print ("Your name is " ^ name, w2) in
w3
しかし、これは好きではありません:
let main w =
let w1 = print ("What's your name?", w) in
let (name, w2) = read w in
let w3 = print ("Your name is " ^ name, w2) in
w3
(wが2回使用されているため)
副作用のあるすべての組み込み関数は、ワールド値を取り、返します。副作用のあるすべての関数は組み込みであるか、副作用のある他の関数を呼び出すため、これは、副作用のあるすべての関数がワールドを取得して返す必要があることを意味します。
このように、同じ引数で副作用のある関数を2回呼び出すことはできず、参照透過性に違反することはありません。
2)副作用のあるすべての操作をそのモナド内で実行する必要があるIOモナド。
このアプローチでは、副作用のあるすべての操作にタイプがありio something
ます。たとえば、型をprint
持つ関数であり、型はです。string -> io unit
read
io string
実行する演算の値にアクセスする唯一の方法は、IO演算を1つの引数として、結果を他の引数として処理する関数を使用して、「モナディックバインド」演算(たとえば、haskellでは>> =と呼ばれる)を使用することです。オペランド。
上記の例は、モナディックIOを使用すると次のようになります。
let main =
(print "What's your name?") >>=
(lambda () -> read >>=
(lambda name -> print ("Your name is " ^ name)))
関数型言語でI/Oを処理するために利用できるいくつかのオプションがあります。
- 純粋にならないでください。多くの関数型言語は純粋関数型ではありません。 関数型プログラミングを強制するのではなく、サポートするだけではありません。これは、関数型プログラミングにおけるI/Oの問題に対する最も一般的な解決策です。(例:Lisp、Scheme、Standard ML、Erlangなど)
- ストリーム変換。初期のHaskellI/Oはこのように行われました。詳細については、以下のリンクを確認してください。(ヒント:おそらくそうではありません。)
- 継続渡しI/O(他の回答で言及されている「世界通過」)。これでは、参照整合性を維持するために必要な「異なる値」として機能するデータのトークンをI/Oとともに渡します。これは、メモリが機能する場合、いくつかのML方言で使用されます。
- 上記の「継続」または「世界」は、さまざまなデータ型にラップできます。最も有名なのは、Haskellでのこの役割でのモナドの使用です。これは、概念的には同じことですが、「世界」/「継続」状態変数を追跡するという面倒な作業が削除されていることに注意してください。
これらを徹底的に分析する研究論文があります。
機能的I/Oは現在進行中の研究分野であり、この問題に興味深く、気が遠くなるような方法で対処する他の言語があります。 ホーア論理は、いくつかの研究言語で使用されています。その他(Mercuryなど)は、一意性の入力を使用します。さらに他のもの( Cleanなど)はエフェクトシステムを使用します。これらのうち、私は水星のみに非常に限られた露出しか持っていないので、詳細についてコメントすることはできません。CleanのI/Oシステムについて詳しく説明している論文がありますが、その方向性に興味がある場合は。
私の理解の限りでは、関数型言語で副作用が必要な場合は、それらを明示的にコーディングする必要があります。
副作用は参照透過性を損なうので、関数型言語のポイントに反しませんか?
関数型言語によって異なります:
標準MLでは、Fortran、Algol、Pascal、Cなどのほとんどの手続き型言語のような副作用を自由に使用できます。
Haskellは、、、などの抽象データ型を使用して副作用を制限します。これは、参照透過性を維持するのに役立ちます。
IO
ST
STM
Cleanは副作用も制限しますが、拡張型システムでこれを行います。
Coqが使用する関数型言語--Gallina--は、副作用へのアクセスをまったく提供しません。
関数型言語はどのように副作用をモデル化しますか?
定期的に言及されていないアプローチの1つは、疑似データに依存しています。アクセス可能な構造化値(通常はツリー)で伝達される個々の使い捨て抽象値であり、副作用は各抽象値が最初に使用されたときにのみ発生します。詳細については、関数型プログラミング言語における参照透過性を備えたF.ウォーレンバートンの非決定性を参照してください。実用的な例はGHCにもあります:そのUnique
名前-suppyタイプ。