プログラマーへの参照透過性の利点は何ですか?


18

プログラミングにおいて、参照の透明性の利点は何ですか?

RTは、機能的パラダイムと命令型パラダイムの大きな違いの1つであり、機能的パラダイムの支持者が命令型パラダイムに対する明確な利点としてよく使用します。しかし、彼らの努力のすべてにおいて、これらの支持者は、それがプログラマーとしての私にとってなぜ利益であるかを決して説明しません。

確かに、彼らはそれがどのように「純粋」で「エレガント」であるかについて学問的な説明をしますが、それほど「純粋でない」コードよりもそれをどのように改善しますか?毎日のプログラミングでどのようなメリットがありますか?

注: これは、参照透過性とは何ですか? 後者はRTとは何かというトピックに対処しますが、この質問はその利点を扱います(それほど直感的ではないかもしれません)。




3
参照の透明性により、等式推論を使用して、1)コードのプロパティを証明し、2)プログラムを作成できます。Haskellに関する本がいくつかあります。著者は、関数にフルフィルメントを求める方程式から始め、方程式の推論を使用して、その関数の実装を取得する必要があります。これが「日常」プログラミングにどれだけ適用できるかは、おそらくコンテキストに依存します
...-Bakuriu

2
@err関数を2回呼び出すことは、その値を変数に格納してから、その変数を2回使用することと同じかどうかを知っているため、リファクタリングが簡単なコードが好きですか?それはあなたの日々のプログラミングにとって有益だと思いますか?
アンドレスF.

利点は、参照の非透明性について考える時間を無駄にする必要がないことです。レジスタの割り当てについて考える時間を無駄にする必要がないという変数の利点が好きです。
user253751

回答:


37

利点は、純粋な関数によりコードの推論が容易になることです。または、言い換えると、副作用によりコードの複雑さが増します。

computeProductPriceメソッドの例を見てみましょう。

純粋なメソッドは、製品の数量、通貨などを要求します。メソッドが同じ引数で呼び出されると、常に同じ結果生成されることを知っています。

  • それをキャッシュして、キャッシュされたバージョンを使用することもできます。
  • 値を変更せずに、実際に必要なときに遅延させて呼び出しを延期することができます。
  • 副作用がないことを知って、メソッドを複数回呼び出すことができます。
  • 必要なのは引数だけであることを知っているため、メソッド自体を世界から隔離して推論することができます。

非純粋なメソッドは、使用とデバッグがより複雑になります。引数以外の変数の状態に依存し、それらを変更する可能性があるため、複数回呼び出されたときに異なる結果を生成したり、まったく呼び出されなかったり、あまりにも早くまたは遅すぎて呼び出されたときに同じ動作をしないことがあります。

フレームワークに数値を解析するメソッドがあると想像してください:

decimal math.parse(string t)

以下に依存するため、参照の透明性はありません。

  • ナンバリングシステムを指定する環境変数、つまり、Base 10または他の何か。

  • math解析する数値の精度を指定するライブラリ内の変数。そのため、の値で1は、文字列"12.3456"を解析するとが得られ12.3ます。

  • カルチャ。予想されるフォーマットを定義します。例えば、とfr-FR、解析が"12.345"与える12345、区切り文字がする必要があるため,ではなく、.

そのような方法で作業することがどれほど簡単か難しいか想像してみてください。同じ入力で、メソッドを呼び出す瞬間に応じて根本的に異なる結果を得ることができます。何かが環境変数を変更したり、カルチャを切り替えたり、異なる精度を設定したりするためです。メソッドの非決定的な特性は、より多くのバグとより多くのデバッグの悪夢につながります。いくつかの並列コードが8進数を解析していたので、呼び出して答えとしてmath.parse("12345")取得5349するのは良くありません。

この明らかに壊れた方法を修正するには?参照の透明性を導入する。言い換えると、グローバル状態を取り除き、すべてをメソッドのパラメーターに移動することにより:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

メソッドが純粋になったので、メソッドをいつ呼び出しても、同じ引数に対して常に同じ結果が生成されることがわかります。


4
ただの補遺:参照透過性は、関数だけでなく、言語のすべての式に適用されます。
ガーデンヘッド

3
透明性には限界があることに注意してください。作るpacket = socket.recv()参照透明ことは、むしろ機能のポイントを破ります。
マーク

1
culture = cultures.invariantである必要があります。あなたがない限りしたい誤って米国のみで正しく動作するソフトウェアを作成します。
user253751

@immibis:うーん、いい質問です。解析ルールは何になりますinvariantか?それらはの場合と同じen_usで、その場合、わざわざ、または他の国に対応し、その場合、どの国で、なぜこのen_us国ではなく、いずれかの国と一致しない特定のルールがあります、それは役に立たないでしょう。12,345.67との間に「真の答え」はあり12 345,67ません。「デフォルトルール」はいくつかの国では機能しますが、他の国では機能しません。
Arseni Mourzenko

3
@ArseniMourzenkoこれは一般に「最も低い共通分母」であり、多くのプログラミング言語が使用する構文に似ています(これも文化に不変です)。1234512345として解析し、12 345または12,345または12.345エラーです。12.345を使用するプログラミング言語の規則に従って、不変の浮動小数点数として解析されると、常に12.345が生成されます。小数点区切りとして。文字列は、Unicodeコードポイントで大文字と小文字を区別してソートされます。等々。
user253751

11

コードのポイントにブレークポイントを追加して、デバッガーでアプリを実行して、何が起きているのかをよく調べますか?その場合、主にデザインで参照透過性(RT)を使用していないためです。そして、それが何をするかを理解するためにコードを実行する必要があります。

RTの重要なポイントは、コードが非常に決定論的であるということです。つまり、同じ入力のセットに対してコードを読み取り、そのコードが何をするかを毎回実行できるということです。いくつかの変数が単一の関数を超えるスコープを持つ変数の変更を追加し始めると、コードを読むことができなくなります。そのようなコードは、それが実際にどのように機能するかを理解するために、頭またはデバッガーで実行する必要があります。

コードの読み取りと推論が簡単であればあるほど、バグの維持と発見が簡単になるため、あなたとあなたの雇用主の時間とお金を節約できます。


10
「変数の変更を追加すると、その一部は単一の機能を超えているため、コードを読み取るだけでなく、コードを実際に動作させるために、頭またはデバッガーで実行する必要があります。 ": いい視点ね。言い換えれば、参照の透過性は、コードの一部が同じ入力に対して常に同じ結果を生成することを意味するだけでなく、生成された結果がそのコードの唯一の効果であり、他の隠れた側面がないことも意味します別のモジュールで遠く離れて定義されている変数を変更するような効果。
ジョルジオ

それは良い点です。読みやすさや理由はコードのやや曖昧で主観的な属性であるため、コードが引数を読み取る/理由を付けるのが単純であることに少し問題があります。
エアルロス

変異変数の追加を開始すると、そのうちのいくつかは単一の関数を超えるスコープを持ちます が、変数スコープが関数に対してローカルである場合でも割り当て操作が推奨されないのはなぜですか?
rahulaga_dev

9

人々は「推論するのがより簡単」という用語を放り投げますが、それが何を意味するのかを決して説明しません。次の例を考えてみましょう。

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

あるresult1result2同じか違うのですか?参照の透明性がないと、あなたは何も知りません。実際に本体を読んでfoo確認する必要があり、場合によっては関数foo呼び出しの本体なども読む必要があります。

人々は、彼らがそれに慣れているので、この負担に気づくが、あなたは月または2のために純粋に機能的な環境で作業を行っている場合、あなたはそれを感じることが戻ってきて、そしてそれはしていない巨大な契約

参照の透明性の欠如を回避するために人々が行う非常に多くの防御メカニズムがあります。私の小さな例では、result1それが変わるかどうかわからないので、私は記憶に残したいかもしれません。次に、2つの状態を持つコードがあります:before result1が保存された後とafter。参照の透過性を使用すると、再計算に時間がかからない限り、簡単に再計算できます。


1
参照の透過性により、foo()の呼び出しの結果について推論し、同じであるかどうかresult1を知ることができると述べましたresult2。別の重要な側面は、foo("bar", 12)参照が透過的である場合、この呼び出しが他の場所で何らかの効果を生み出したかどうかを自問する必要がないことです(変数を設定しますか?ファイルを削除しましたか?)。
ジョルジオ

私がよく知っている唯一の「参照整合性」には、リレーショナルデータベースが関係しています。
マーク

1
@Markタイプミスです。カールは、彼の残りの答えから明らかなように、参照の透明性を意味しました。
アンドレスF.

6

参照の透明性は、関数型プログラミングだけでなく、関数を使用するすべての人にとって良いことです。なぜなら、それは最小限の驚きの原則に従っているからです。

関数があり、考慮する必要がある外部要因がないため、関数が何をするのかをよりよく推論できます。与えられた入力に対して、出力は常に同じになります。私の命令型言語でさえ、私はこのパラダイムに可能な限り従おうとしますが、これから基本的に自動的に続く次のことは、私が時々走る恐ろしい1000+行関数の代わりに小さなわかりやすい関数です。

これらの大規模な機能は魔法のように機能しますが、それらは壮大な方法で破損する可能性があるため、それらに触れることを恐れています。

したがって、純粋な関数は、関数型プログラミングだけでなく、すべてのプログラムのためのものです。

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