参照の透明性はどのように適用されますか?


8

FP言語では、同じパラメーターを使用して関数を何度も呼び出すと、同じ結果が繰り返し返されます(つまり、参照透過性)。

しかし、このような関数(疑似コード):

function f(a, b) {
    return a + b + currentDateTime.seconds;
}

同じパラメータに対して同じ結果を返すことはありません。

これらのケースはFPでどのように処理されますか?

参照の透明性はどのように適用されますか?それともそうではなく、プログラマが自分で行動するかどうかに依存しますか?


5
言語に依存します。参照透過性をまったく適用しないものもあれば、型システムを使用して参照透過関数をIOから分離するものもあります(HaskellのモナドやClean
jkの

1
良い型のシステムが呼び出しを防止することができますcurrentDateTimeからf(例えばHaskellのような)参照透明性を強制する言語で、。私は他の誰かがより詳細な答えを提供するようにします:)(ヒント:currentDateTimeIOを実行し、これはそのタイプで表示されます)
Andres F.

回答:


20

aand bNumbersですが、をcurrentDateTime.seconds返しますIO<Number>。これらのタイプには互換性がなく、それらを一緒に追加することはできません。そのため、関数は型付けされておらず、単にコンパイルされません。少なくとも、Haskellのような静的型システムを備えた純粋な言語でそれを行う方法です。ML、Scala、F#などの不純な言語では、参照の透過性を確保するのはプログラマの責任です。もちろん、ClojureやSchemeなどの動的に型付けされた言語では、参照の透過性を強制する静的型システムはありません。


それで、コンパイラ/タイプシステムに、HaskellのようにScalaでの参照の透明性を保証させることは不可能ですか?
cib

8

Haskellのアプローチを説明します(Haskellの専門家ではないため、直感が100%正しいかどうかはわかりませんが、修正は歓迎されます)。

コードは次のようにHaskellで記述できます。

import System.CPUTime

f :: Integer -> Integer -> IO Integer
f a b = do
          t <- getCPUTime
          return (a + b + (div t 1000000000000))

では、参照の透明性はどこにあるのでしょうか? f2つの整数aとを指定するとb、戻り値の型でわかるように、アクションを作成する関数ですIO Integer。2つの整数が与えられた場合、このアクションは常に同じであるため、整数のペアをIOアクションにマッピングする関数は参照的に透過的です。

このアクションが実行されると、生成される整数値は現在のCPU時間に依存します。アクションの実行は機能アプリケーションではありません。

要約:Haskellでは、純粋な関数を使用して、複雑なアクション(シーケンス処理、アクションの作成など)を参照透過的な方法で構築および結合できます。ここでも、上記の例では、純粋な関数fは整数を返さないことに注意してください。アクションを返します。

編集

JohnDoDo質問に関する詳細。

「アクションの実行が機能適用ではない」とはどういう意味ですか?

T1、T2、Tn、Tのセットが与えられた場合、関数fは、T1 x T2 x ... x Tnの各タプルにTの1つの値を関連付けるマッピング(関係)です。したがって、関数アプリケーションは、いくつかの入力値を指定して出力値を生成します。このメカニズムを使用すると、を評価するを作成できます。たとえば、値10を評価した結果です4 + 6。この方法で値を値にマッピングする場合、いかなる種類の入力/出力も実行しないことに注意してください。

Haskellでは、アクションは特別なタイプの値であり、アクションを操作する適切な純粋な関数を含む式を評価することによって構築できます。このように、Haskellプログラムは、main関数を評価することによって取得される複合アクションです。このメインアクションにはタイプがありIO ()ます。

この複合アクションが定義されると、別のメカニズム(関数アプリケーションではない)がアクションの呼び出し/実行に使用されます(ここを参照)。プログラム全体の実行は、サブアクションを呼び出すことができるメインアクションを呼び出した結果です。この呼び出しメカニズム(内部的な詳細は不明)は、必要なすべてのIO呼び出しを実行し、場合によっては端末、ディスク、ネットワークなどにアクセスします。

例に戻ります。上記の関数fは整数を返さないため、IOを実行して同時に整数を返す関数を作成することはできません。2つのうち1つを選択する必要があります。

できることは、によって返されたアクションをf 2 3より複雑なアクションに埋め込むことです。たとえば、そのアクションによって生成された整数を出力する場合は、次のように書くことができます。

main :: IO ()
main = do
          x <- f 2 3
          putStrLn (show x)

do表記はメイン関数によって返されたアクションは、2つの小さい方のアクションの連続合成により得られることを示し、x <-表記は最初のアクションで生成値が第2のアクションに渡さなければならないことを示しています。

2番目のアクションで

putStrLn (show x)

名前xは、アクションの実行によって生成された整数にバインドされます

f 2 3

重要な点は、最初のアクションが呼び出されたときに生成される整数はIOアクション内にのみ存在できることです。これは、あるIOアクションから次のIOアクションに渡すことができますが、プレーン整数値として抽出することはできません。

main上記の関数と次の関数を比較してください:

main = do
      let y = 2 + 3
      putStrLn (show y)

この場合、アクションは1つしかありません。つまりputStrLn (show y)y純粋な関数を適用した結果にバインドされます+。このメインアクションを次のように定義することもできます。

main = putStrLn "5"

したがって、構文が異なることに注意してください

x <- f 2 3    -- Inject the value produced by an action into
              -- the following IO actions.
              -- The value may depend on when the action is
              -- actually executed. What happens when the action is
              -- executed is not known here: it may get user input,
              -- access the disk, the network, the system clock, etc.

let y = 2 + 3 -- Bind y to the result of applying the pure function `+`
              -- to the arguments 2 and 3.
              -- The value depends only on the arguments 2 and 3.

概要

  • Haskellでは、純粋な関数を使用して、プログラムを構成するアクションを構築します。
  • アクションは特別なタイプの値です。
  • アクションは純粋な関数を適用することによって構築されるため、アクションの構築は参照に対して透過的です。
  • アクションが作成されたら、別のメカニズムを使用して呼び出すことができます。

2
executing actions is NOT function applicationフレーズを少し詳しく説明していただけませんか?私の例では、整数を返すつもりでした。整数を返すとどうなりますか?
JohnDoDo 2013年

2
少なくとも怠惰なHaskellの@JohnDoDo(私は熱意のある透明な言語と話すことはできません)絶対にそうでなければならないまで、何も実行されません。これは、例ではGiorgioがそのアクションを取得していることを示しており、不愉快なことを行う以外に、IOアクションから数値を取得することはできません。その結果、プログラムが終了するまで、そのアクションを他のIOアクションと組み合わせる必要があります。 Mainを使って; サプライズサプライズはIOアクションです。Haskell自体がIOアクションを実行しますが、その実行全体を通して、必要な部分だけが必要なときにだけ実行されます。
ジミー・ホッファ

@JohnDoDo整数を返したい場合、fIO Integer(整数ではなくアクション)を持つことはできません。ただし、typeが指定されている「現在の日付」を呼び出すことはできませんIO Integer
Andres F.

また、出力として取得するIO Integerを通常のIntegerに変換し直して、純粋なコードで再び使用することはできません。基本的に、IOモナドで起こっていることはIOモナドにとどまります。(これには例外があり、unsafePerformIOを使用して値を戻すことができますが、そうすることで、本質的にコンパイラーに「大丈夫です。これは参照的に透過的です」と伝えます。コンパイラーはあなたを信じて、次に関数を使用するとき、現在の時刻ではなく、以前に計算された関数の値がフェッチされる場合があります。)
Michael Shaw

1
最後の例が示すのは、一致するShow利用可能なインスタンスがないことです。ただし、簡単に追加できます。その場合、コードは正常にコンパイルおよび実行されます。に関するIOアクションについては、特別なことは何もありませんshow

4

通常のアプローチは、コールグラフ全体を通じて関数が純粋であるかどうかをコンパイラが追跡できるようにし、純粋でないものを実行する関数を純粋であると宣言するコードを拒否することです(「純粋でない関数の呼び出し」も純粋でないものです)。

Haskellはすべてを言語自体で純粋にすることによってこれを行います。言語自体ではなく、不純なものがランタイムで実行されます。この言語は、純粋な関数を使用してIOアクションを構築するだけです。次に、ランタイムmainは指定されたMainモジュールから呼び出された純粋な関数を見つけて評価し、結果の(純粋でない)アクションを実行します。

他の言語の方が実用的です。一般的なアプローチは、関数に「純粋」のマークを付ける構文を追加し、そのような関数内の不純なアクション(変数の更新、不純な関数の呼び出し、I / O構造)を禁止することです。

あなたの例でcurrentDateTimeは、は不純な関数(または関数のように動作するもの)であるため、純粋なブロック内で呼び出すことは禁止されており、コンパイラエラーが発生します。Haskellでは、関数は次のようになります。

f :: Int -> Int -> IO Int
f a b = do
    ct <- getCurrentTime
    return (a + b + timeSeconds ct)

次のように、IO以外の関数でこれを実行しようとした場合:

f :: Int -> Int -> Int
f a b =
    let ct = getCurrentTime
    in a + b + timeSeconds ct

...コンパイラは、あなたのタイプはチェックアウトしていないことを言うだろう- getCurrentTime型であるIO Time、ないTime、しかしtimeSecondsを期待Time。言い換えると、Haskellはその型システムを利用して、純粋性をモデル化(および適用)します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.