web-dev-qa-db-ja.com

同じ入力が常に同じ出力を返すが副作用もある関数を何と呼びますか?

次のような通常の純粋な関数があるとします。

function add(a, b) {
  return a + b
}

そして、私たちはそれが副作用を持つようにそれを変更します

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

純粋な関数を「副作用のない関数」と呼ぶことがよくあるので、私が知る限り、それは純粋な関数とは見なされません。ただし、同じ入力に対して同じ出力を返すという点では、純粋な関数のように動作します。

このタイプの関数には別の名前がありますか、それとも名前が付いていませんか、それとも実際には純粋であり、私は純粋性の定義について誤解していますか?

43
m0meni

純度の普遍的な定義についてはわかりませんが、Haskell(プログラマーが純度や参照の透明性などを気にする傾向がある言語)の観点からは、最初の関数のみが「純粋"。addの2番目のバージョンは純粋ではありません。だからあなたの質問に答えて、私はそれを「不純」と呼びます;)

この定義によれば、純粋な関数は次のような関数です。

  1. 入力のみに依存します。つまり、同じ入力を指定すると、常に同じ出力が返されます。
  2. 参照透過的です。関数はその値で自由に置き換えることができ、プログラムの「動作」は変更されません。

この定義では、2番目の関数は規則2に違反しているため、純粋とは見なされないことは明らかです。つまり、次の2つのプログラムは同等ではありません。

function f(a, b) { 
    return add(a, b) + add(a, b);
}

そして

function g(a, b) {
    c = add(a, b);
    return c + c;
}

これは、両方の関数が同じ値を返す場合でも、関数fはデータベースに2回書き込みますが、gは1回書き込むためです。データベースへの書き込みがプログラムの観察可能な動作の一部である可能性が高いです。その場合、2番目のバージョンのaddが「純粋」ではないことを示しました。

データベースへの書き込みがプログラムの動作の観察可能な部分でない場合、addの両方のバージョンは同等で純粋と見なすことができます。しかし、データベースへの書き込みが問題にならないシナリオは考えられません。ロギングも重要です!

84
Andres F.

同じ入力が常に同じ出力を返しますが、副作用もある関数を何と呼びますか?

このような関数は

確定的

入力から動作を完全に予測できるアルゴリズム。

termwiki.com

状態について:

使用する関数の定義に応じて、関数には状態がありません。オブジェクト指向の世界から来た場合は、x.f(y)がメソッドであることを思い出してください。関数としては、f(x,y)のようになります。囲まれたレキシカルスコープを持つクロージャーを使用している場合は、不変状態も関数式の一部である可能性があることに注意してください。関数の決定論的な性質に影響を与えるのは、変更可能な状態のみです。したがって、f(x) = x + 1は、1が変化しない限り、確定的です。1が格納されている場所は関係ありません。

関数はどちらも確定的です。最初の関数も純粋な関数です。あなたの秒は純粋ではありません。

純粋な機能

  1. 関数は常に、同じ引数値が与えられた場合、同じ結果値を評価します。関数の結果の値は、プログラムの実行中に、またはプログラムの異なる実行間で変化する可能性のある非表示の情報や状態に依存したり、I/Oデバイスからの外部入力に依存したりすることはできません。

  2. 結果の評価は、変更可能なオブジェクトの変異やI/Oデバイスへの出力など、意味的に観察可能な副作用や出力を引き起こしません。

wikipedia.org

ポイント1は 確定的 を意味します。ポイント2は 参照透明度 を意味します。これらは、純粋な関数が引数と戻り値の変更のみを許可することを意味します。何も変化を引き起こしません。他には何も変更されません。

18
candied_orange

副作用を気にしない場合、それは参照的に透過的です。もちろん、あなたが気にしないが他の誰かが気にする可能性があるので、この用語の適用は文脈に依存します。

あなたが説明するプロパティを正確に表す一般的な用語はわかりませんが、重要なサブセットはべき等であるものです。コンピュータサイエンスでは、数学*とは少し異なりますが、-べき等関数は、同じ効果で繰り返すことができるものです。つまり、何回も実行した場合のnett副作用の結果は、1回実行した場合と同じです。

したがって、副作用が特定の行の特定の値でデータベースを更新したり、内容が完全に一貫したファイルを作成したりする場合、それはidempotentになりますが、データベース、またはファイルに追加された場合、そうではありません。

べき等関数の組み合わせは、全体としてべき等である場合とそうでない場合があります。

* べき等級の使用は、コンピュータサイエンスでは数学とは異なり、その概念が有用であるために採用された数学用語の誤った使用に起因しているようです。

9
Jon Hanna

私はそのような関数がどのように呼び出されるか(またはいくつかの体系的な名前があるかどうか)わかりませんが、純粋ではない関数を呼び出します(他の答えがひどいので)、同じパラメーター「その関数パラメータ」(そのパラメータおよび他のいくつかの状態の関数と比較)。単に関数と呼んでいますが、残念ながらプログラミングの文脈で「関数」とは、実際の関数である必要がないことを意味します。

3
user470365

基本的には、不純物を気にするかどうかによって異なります。このテーブルのセマンティクスで、エントリの数が気にならない場合、それは純粋です。そうでなければ、それは純粋ではありません。

または別の言い方をすれば、純粋さに基づく最適化がプログラムのセマンティクスを壊さない限り問題ありません。

より現実的な例は、この関数をデバッグしようとして、ロギングステートメントを追加した場合です。技術的には、ロギングは副作用です。ログはそれを不純にしますか?番号。

2
DeadMG

私が尋ねるのに最適なのは、それをどのように呼び出すかではなく、analyzeこのようなコードの一部をどのように呼び出すかです。そして、そのような分析における私の最初の重要な質問は次のようになります。

  • 副作用は、関数への引数、または副作用の結果に依存しますか?
    • No:「効果的な関数」は、純粋な関数、効果的なアクション、およびそれらを組み合わせるためのメカニズムにリファクタリングできます。
    • はい:「有効な関数」は、モナディックな結果を生成する関数です。

これはHaskellで説明するのは簡単です(そしてこの文は冗談の半分にすぎません)。 「いいえ」の場合の例は次のようになります。

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

この例では、実行するアクション("I'm doubling some number"の行を出力)は、xと結果の関係に影響を与えません。これは、この方法で(Applicativeクラスとその*>演算子を使用して)リファクタリングできることを意味し、関数と効果が実際に直交していることを示しています。

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

したがって、この場合、私は個人的には、純粋な関数を因数分解できるケースだと言います。 Haskellプログラミングの多くはこれについてです。効果的なコードから純粋な部分を除外する方法を学びます。

純粋な部分と有効な部分が直交しない「はい」のソートの例:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

これで、出力する文字列はxの値に依存します。 function部分(xに2を掛ける)は、効果にまったく依存しないため、因数分解できますそれを出す:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

他の例を詳しく説明することもできますが、これで私が始めたポイントを説明するのに十分であることを願っています。何かを「呼び出す」のではなく、純粋な部分と効果的な部分がどのように関連しているかを分析し、そうである場合はそれらを除外しますあなたの利点に。

これがHaskellがMonadクラスを非常に広範囲に使用する理由の1つです。モナドは、(とりわけ)この種の分析とリファクタリングを実行するためのツールです。

1
sacundim