web-dev-qa-db-ja.com

参照透過性とは何ですか?

私は命令パラダイムでそれを見てきました

f(x)+f(x)

次と同じではない可能性があります。

2 * f(x)

しかし、機能的なパラダイムでは、それは同じであるべきです。私はPythonおよび Scheme で両方のケースを実装しようとしましたが、私にとってはそれらは同じようにかなり簡単に見えます。

与えられた関数との違いを指摘できる例は何でしょうか?

38
asgard

関数と呼ばれる参照透過性は、その引数の値を見ることだけでその関数を適用した結果を判別できることを示します。参照透過的な関数は、任意のプログラミング言語で記述できます。 Python、Scheme、Pascal、C。

一方、ほとんどの言語では、参照を透過しない関数を作成することもできます。たとえば、次のPython関数:

_counter = 0

def foo(x):
  global counter

  counter += 1
  return x + counter
_

参照的に透過的ではなく、実際には

_foo(x) + foo(x)
_

そして

_2 * foo(x)
_

引数xに対して、異なる値を生成します。これは、関数がグローバル変数を使用および変更するため、各呼び出しの結果は、関数の引数だけでなく、この変化する状態に依存するためです。

純粋に関数型の言語であるHaskellは、式の評価を厳密に分離し、ここで純粋な関数が適用され、常に参照的に透過的ですaction execution(特別な値の処理)から、参照的に透過的ではありません。つまり、同じアクションを実行すると毎回異なる結果を持っています。

つまり、Haskell関数の場合

_f :: Int -> Int
_

そして、任意の整数x、それは常に真です

_2 * (f x) == (f x) + (f x)
_

アクションの例は、ライブラリ関数getLineの結果です。

_getLine :: IO String
_

式の評価の結果、この関数(実際には定数)はまず_IO String_型の純粋な値を生成します。このタイプの値は、他のすべての値と同じです。値を渡したり、データ構造に入れたり、特殊な関数を使用して構成したりできます。たとえば、次のようなアクションのリストを作成できます。

_[getLine, getLine] :: [IO String]
_

アクションは、次のように書くことでHaskellランタイムに実行するように指示できるという点で特別です。

_main = <some action>
_

この場合、Haskellプログラムが開始されると、ランタイムはmainにバインドされたアクションをウォークスルーし、executesして、副作用が発生する可能性があります。したがって、同じアクションを2回実行すると、ランタイムが入力として取得する内容に応じて異なる結果が生成される可能性があるため、アクションの実行は参照透過的ではありません。

Haskellの型システムのおかげで、アクションは別の型が期待されるコンテキストでは決して使用できず、その逆も同様です。したがって、文字列の長さを知りたい場合は、length関数を使用できます。

_length "Hello"
_

5を返しますが、端末から読み取った文字列の長さを知りたい場合は、書き込むことはできません。

_length (getLine)
_

タイプエラーが発生するため:lengthはリストタイプの入力を期待します(そして文字列は実際にはリストです)が、getLineはタイプ_IO String_(アクション)の値です。このようにして、型システムは、getLine(コア言語の外部で実行され、非参照透過である可能性がある)のようなアクション値を、Int型の非アクション値内に隠すことができないようにします。

[〜#〜]編集[〜#〜]

Exiztの質問に答えるために、コンソールから行を読み取ってその長さを出力する小さなHaskellプログラムを次に示します。

_main :: IO () -- The main program is an action of type IO ()
main = do
          line <- getLine
          putStrLn (show (length line))
_

メインアクションは、順次実行される2つのサブアクションで構成されています。

  1. タイプ_IO String_のgetline
  2. 2番目は、引数でString -> IO ()タイプの関数putStrLnを評価することによって構築されます。

より正確には、2番目のアクションは

  1. lineを最初のアクションによって読み取られた値にバインドし、
  2. 純粋な関数length(整数として長さを計算)を評価し、次にshow(整数を文字列に変換)を評価します。
  3. putStrLnの結果に関数showを適用してアクションを作成します。

この時点で、2番目のアクションを実行できます。 「Hello」と入力すると、「5」が出力されます。

_<-_表記を使用してアクションから値を取得する場合、その値は別のアクション内でのみ使用できることに注意してください。あなたは書くことができません:

_main = do
          line <- getLine
          show (length line) -- Error:
                             -- Expected type: IO ()
                             --   Actual type: String
_

show (length line)のタイプはStringですが、do表記ではアクション(_IO String_のgetLine)の後に別のアクションが続く必要があります(例:タイプputStrLn (show (length line))IO ()) 。

編集2

JörgW Mittagの参照の透明性の定義は、私よりも一般的です(私は彼の答えを支持しました)。質問の例は関数の戻り値に焦点を当てているため、制限付きの定義を使用し、この側面を説明したいと思います。ただし、RTは、一般にプログラム全体の意味を指します。グローバル状態の変更や、式の評価によって引き起こされる環境(IO)との相互作用を含みます。したがって、正しい一般的な定義、その答えを参照してください。

62
Giorgio
_def f(x): return x()

from random import random
f(random) + f(random) == 2*f(random)
# => False
_

しかし、それがnot参照透過性の意味です。 RTは、プログラムの意味を変更せずに、プログラム内のany式をその式の評価結果(またはその逆)で置き換えることができることを意味します。

たとえば、次のプログラムを見てください。

_def f(): return 2

print(f() + f())
print(2)
_

このプログラムは参照透過的です。 f()の一方または両方を_2_で置き換えることができますが、それでも同じように機能します。

_def f(): return 2

print(2 + f())
print(2)
_

または

_def f(): return 2

print(f() + 2)
print(2)
_

または

_def f(): return 2

print(2 + 2)
print(f())
_

すべて同じように動作します。

まあ、実際には、私はだまされました。プログラムの意味を変更することなく、printの呼び出しをその戻り値(まったく値ではない)で置き換えることができるはずです。しかし、明らかに、2つのprintステートメントを削除すると、プログラムの意味が変わります。以前は、何かを画面に表示しましたが、表示しませんでした。 I/Oは参照透過的ではありません。

簡単な経験則は次のとおりです。プログラムが意味を変更せずに、式、部分式、またはサブルーチンの呼び出しを、その式、部分式、またはサブルーチンの呼び出しの戻り値で置き換えることができる場合は、参照があります。透明性。これが意味することは、実質的に言えば、I/Oを実行できず、変更可能な状態を設定できず、副作用も発生しないということです。すべての式で、式の値は、式の構成要素の値にのみ依存する必要があります。また、すべてのサブルーチン呼び出しで、戻り値は引数のみに依存する必要があります。

25
Jörg W Mittag

この回答の一部は、私のGitHubアカウントでホストされている 関数型プログラミングの未完成のチュートリアル から直接取得されています。

関数は、同じ入力パラメーターが指定された場合に常に同じ出力(戻り値)を生成する場合、参照透過的であるといいます。純粋な関数型プログラミングの存在理由を探している場合、参照の透過性は良い候補です。代数、算術、および論理の数式で推論する場合、このプロパティは、等しいの代わりに等しいという代用性とも呼ばれます。非常に基本的に重要なので、通常は当然と見なされます...

簡単な例を考えてみましょう:

x = 42

純粋な関数型言語では、等号の左側と右側は、どちらの方法でも相互に代入可能です。つまり、Cのような言語とは異なり、上記の表記は真に平等を主張します。これにより、数式と同じようにプログラムコードを推論できるようになります。

から Haskell wiki

純粋な計算では、呼び出されるたびに同じ値が生成されます。このプロパティは 参照透過性 と呼ばれ、コードに対して等式推論を行うことを可能にします...

これと対照的に、Cのような言語で実行される操作のタイプは、破壊的代入と呼ばれることがあります。

pureという用語は、この議論に関連する式のプロパティを説明するためによく使用されます。関数が純粋と見なされるためには、

  • 副作用を示すことは許可されていません。
  • 参照に対して透過的でなければなりません。

多くの数学の教科書に見られるブラックボックスのメタファーによると、関数の内部は完全に外界から遮断されています。副作用とは、関数または式がこの原則に違反している場合です。つまり、プロシージャは他のプログラムユニットと何らかの方法で通信できます(たとえば、情報を共有および交換するため)。

要約すると、参照の透過性は、関数がtrueのように動作するために必要であり、数学関数もプログラミング言語のセマンティクスで使用されます。

1
yesthisisuser