同じ入力が常に同じ出力を返すだけでなく、副作用がある関数を何と呼びますか?


43

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

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

そして、副作用があるように変更します

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

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

このタイプの関数には別の名前がありますか、名前が付けられていないのですか、それとも実際には純粋であり、純度の定義について間違っていますか?


91
「純粋な機能ではありません」。
ロスパターソン

2
@RossPattersonもそう思っていましたが、参照の透明性について聞いたので、それを自分で守らなかったことがうれしいです。
m0meni

9
場合writeToDatabase、それはこのようにあなたの二作るの例外を引き起こす可能性が失敗するadd前に問題を持っていなかった同じ引数で呼び出された場合でも、時には機能農産物の例外を...ほとんどの時間は、副作用を持つことは、エラーに関連する条件というこの種のブレークを紹介します「入出力の純度」。
バクリウ

25
特定の入力に対して常に同じ出力を提供するものは、決定論と呼ばれます。
njzk2

2
@ njzk2:本当ですが、ステートレスでもあります。ステートフルな決定論的機能は、すべての入力に対して同じ出力が得られない場合があります。例:F(x)true、前の呼び出しと同じ引数で呼び出された場合に返すように定義されています。明らかにシーケンスでは{1,2,2} => {undefined, false, true}これは決定論的ですが、に対して異なる出力を与えますF(2)
MSalters

回答:


85

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

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

  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同等で純粋と見なすことができます。しかし、データベースへの書き込みが重要でないシナリオは考えられません。ロギングも重要です!


1
「入力のみに依存する」冗長な参照透過性ではありませんか?RTが純度と同義であることを意味するのはどれですか?(これについて私が調べるソースが増えるほど、混乱している)
-Ixrec

混乱することに同意します。私は不自然な例しか考えられません。と言うf(x)だけxでなく、いくつかの外部グローバル変数にも依存していますy。次に、fRTのプロパティがある場合、に触れない限り 、その発生値戻り値自由に交換できますy。はい、私の例は疑わしいです。しかし、重要なのは次の場合f(ログまたは書き込み)データベースへの書き込み、それはRTの財産を失う:今、それはあなたがグローバルなままかどうかは関係ありませんy、あなたは手つかずの知っているあなたかどうか、実際に依存して変化する、あなたのプログラムの意味を呼び出すfか、単に戻り値を使用します。
アンドレスF.

ふむ 副作用を除いて純粋な関数があり、2つのサンプルが同等である場合にもそのような動作が保証されているとしましょう。(このケースは実際に出てきたので、仮説ではありません。)私たちはまだ完了していないと思います。
ジョシュア

2
私は、2番目の関数もルール1に違反する可能性があると主張します。使用中のデータベースAPIの言語とエラー処理方法によっては、データベースが利用できない場合や、何らかの理由でdbの書き込みが失敗した場合、関数は何も返さない場合があります。
ザックリプトン

1
Haskellが言及されたので:Haskellではそのような副作用を追加するには、関数のシグネチャを変更する必要があります。(元のデータベースを追加の入力として提供し、変更されたデータベースを関数の追加の戻り値として取得するようなものだと考えてください)。このように型システムで副作用を非常にエレガントにモデル化することは実際に可能であり、それは今日の主流の言語が副作用や純粋さを十分に気にしていないということです。
ComicSansMS

19

同じ入力が常に同じ出力を返すだけでなく、副作用がある関数を何と呼びますか?

そのような関数は呼ばれます

決定論的

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

termwiki.com

状態について:

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

関数は両方とも決定的です。最初の関数も純粋な関数です。あなたの2番目は純粋ではありません。

純粋な機能

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

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

wikipedia.org

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


-1。データベースへの書き込みは、一般的に入力を確認して判断できない外部状態に依存します。データベースはいくつかの理由で利用できない場合があり、操作が成功するかどうかは予測できません。これは確定的な動作ではありません。
Frax

@Fraxシステムメモリが利用できない場合があります。CPUが使用できない可能性があります。決定論的であることは、成功を保証するものではありません。正常な動作が予測可能であることを保証します。
candied_orange

OOMingは機能に固有のものではなく、別のカテゴリの問題です。さて、「純粋な関数」の定義のポイント1を見てみましょう(実際には「決定論的」を意味します):「関数の結果値は、プログラムの実行中またはプログラム、I / Oデバイスからの外部入力に依存することもできません。」データベースはそのような状態なので、OPs機能は明らかにこの条件を満たしていません-それは決定論的ではありません。
Frax

@candied_orange DBへの書き込みが入力のみに依存していた場合、私は同意します。しかし、それはMath.random()です。そのため、(物理的なRNGではなく)PRNGを想定し、PRNGが入力の一部を示している(そうではなく、参照がハードコーディングされている)と見なさない限り、決定論的ではありません。
marstato

1
@candied_orange決定論的状態の引用「入力から動作を完全に予測できるアルゴリズム」。私にとって、IOへの書き込みは間違いなく動作であり、結果ではありません。
-marstato

9

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

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

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

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

* コンピューターサイエンスでの数学とthan 等の異なる使用は、概念が有用であるために採用された数学用語の誤った使用によるものと思われます。


3
「参照透過」という用語は、「誰もが気にする」かどうかよりも厳密に定義されています。私たちは、このような等の接続の問題、欠落している接続文字列、タイムアウト、としてIOの問題を無視したとしても、それはプログラムが置き換えることを示すのは容易まだだ(f x, f x)とはlet y = f x in (y, y)二倍の速あなたはこれらがあると主張する可能性のディスク・スペース-例外のうちに実行されますエッジケースは気にしませんが、このようなあいまいな定義を使用すると、new Random().Next()参照透過的に呼び出すこともできます。
サラ

@kai:コンテキストに応じて、副作用を無視できます。一方、ランダムのような関数の戻り値は副作用ではありません。それはその主な効果です。
ジョルジオ

Random.Next.NETの@Giorgio には確かに副作用があります。まさにその通り。可能であればNext、変数に割り当ててからNext再度呼び出して別の変数に割り当てると、それらが等しくなくなる可能性があります。どうして?呼び出すNextと、Randomオブジェクトの非表示の内部状態が変更されるためです。これは、参照透過性の正反対です。「主な効果」は副作用になり得ないというあなたの主張は理解できません。命令型プログラムでは、命令型プログラムは本質的にステートフルであるため、主な効果が副作用であることよりも一般的です。
サラ

3

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


1
同意した!それは(非公式に)「関数」の数学的定義ですが、あなたが言うように、残念ながら「関数」はプログラミング言語で何かを意味します。
アンドレスF.

2

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

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

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


まあ、それは依存します。たとえば、ログに「INFO f()called」と表示される回数や時間を気にする場合など、ログが不純になる場合があります。よくやる:)
アンドレスF.

8
-1ログは重要です。ほとんどのプラットフォームでは、あらゆる種類の出力が暗黙的に実行スレッドを同期します。プログラムの動作は、他のスレッド書き込み、外部ログライター、場合によってはログ読み取り、ファイル記述子の状態に依存します。それは土のバケツと同じくらい純粋です。
バシレフス

@AndresF。まあ、あなたはおそらく文字通りの回数を気にしないでしょう。おそらく、関数が呼び出された回数だけログに記録されることに注意してください。
DeadMG

@Basilevs関数の動作はそれらにまったく依存していません。ログの書き込みが失敗した場合は、そのまま続行します。
DeadMG

2
ロガーを実行環境の一部として定義するかどうかの問題です。別の例として、デバッガをプロセスにアタッチしてブレークポイントを設定した場合、私の純粋な関数はまだ純粋ですか?デバッガーを使用している人のPOVから、関数には明らかに副作用がありますが、通常、これは「カウントしない」という規則でプログラムを分析します。デバッグに使用されるロギングにも同じことができますが(必要ではありません)、これがトレースがその不純物を隠している理由だと思います。もちろん、監査などのミッションクリティカルなロギング、重大な副作用です。
スティーブジェソップ

1

一番いいのは、それをどう呼ぶかではなく、そのようなコードをどのように分析するかということです。そして、そのような分析における私の最初の重要な質問は次のとおりです。

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

Haskellで説明するのは簡単です(この文は冗談の半分にすぎません)。「no」ケースの例は、次のようなものです。

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機能の一部(乗算x2による)、我々はまだそれを考慮することができますので、しかし、すべての効果に依存しません。

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つです。モナドは(とりわけ)この種の分析とリファクタリングを実行するためのツールです。


-2

副作用を引き起こすことを目的とした関数は、しばしばeffectfulと呼ばれます。例https://slpopejoy.github.io/posts/Effectful01.html


広く認識されている効果的な用語に言及するための答えだけで、それは下票されます....無知は至福です。..
ベンハッチソン

「効果的」とは、その投稿の著者が「副作用がある」ことを意味するように選択した言葉です。彼は自分でそう言います。
ロバートハーベイ

グーグル効果関数は、その用語が広く使用されている多くの証拠を明らかにします。ブログ投稿は、定義としてではなく、多くの例の1つとして提供されました。純粋な関数がデフォルトである関数型プログラミングサークルでは、意図的に副作用のある関数を記述するための積極的な用語が必要です。つまり、純粋さ欠如以上のものです。その用語は効果的です。今、あなた自身が教育を受けていると考えてください。
ベンハッチソン

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