web-dev-qa-db-ja.com

メモ化された純粋な関数自体は純粋と見なされますか?

fn(x)xの素因数のリストを返すなど、高額な処理を行う純粋な関数だとしましょう。

そして、memoizedFn(x)と呼ばれる同じ関数のメモ化バージョンを作成するとします。指定された入力に対して常に同じ結果を返しますが、パフォーマンスを向上させるために以前の結果のプライベートキャッシュを維持します。

正式に言えば、memoizedFn(x)は純粋と見なされますか?

またはFPディスカッションでこのような関数を参照するために使用される他の名前または修飾語がありますか?(つまり、後続の呼び出しの計算の複雑さに影響を与える可能性があるが影響を与えない可能性がある副作用を持つ関数)戻り値。)

48
callum

はい。純粋関数のメモ化されたバージョンも純粋関数です。

関数の純粋さが気にするのは、関数の戻り値に対する入力パラメーターの影響(同じ入力を渡すと常に同じ出力が生成されるはずです)とグローバル状態に関連する副作用(例:端末またはUIまたはネットワークへのテキスト)です。 。計算時間と追加のメモリ使用量は、関数の純粋性とは無関係です。

純粋な関数のキャッシュは、プログラムからはほとんど見えません。関数型プログラミング言語は、純粋な関数を、それが有益であると判断できる場合、メモ化されたバージョンの関数に自動的に最適化できます。実際には、いつメモ化が有益かを自動的に判断することは実際には非常に難しい問題ですが、そのような最適化は有効です。

42
Lie Ryan

ウィキペディアは "Pure Function" を次のプロパティを持つ関数として定義しています:

  • その戻り値は同じ引数と同じです(ローカル静的変数、非ローカル変数、可変参照引数、またはI /からの入力ストリームで変動なし) Oデバイス)。

  • その評価には副作用がありません(ローカル静的変数、非ローカル変数、可変参照引数、またはI/Oストリームの変更なし)。

実質的に、純粋な関数は同じ入力が与えられた場合に同じ出力を返し、は関数の外部に影響を与えません。純粋さの目的では、関数は、同じ入力で同じ出力が返される限り、戻り値を計算します。

Haskellのような機能的に純粋な言語 通常はメモ化を使用 以前に計算された結果をキャッシュすることで関数を高速化します。

20
Robert Harvey

はい、メモ化された純粋な関数は一般に純粋と呼ばれます。これは、Haskellのような言語で特に一般的です。Haskellでは、メモされ、遅延評価され、不変の結果が組み込みの機能です。

重要な注意点が1つあります。メモ化関数はスレッドセーフでなければなりません。そうでない場合、2つのスレッドの両方が呼び出しを試みたときに競合状態が発生する可能性があります。

この方法で「完全に機能する」という用語を使用するコンピュータサイエンティストの1つの例は、次のとおりです 自動ブログに関するConal Elliottによるこのブログ投稿

おそらく驚くべきことに、メモ化は遅延関数型言語で単純かつ純粋に機能的に実装できます。

査読済みの文献には多くの例があり、何十年も前から存在しています。たとえば、1995年のこの論文 「自動メモ化を現実世界のAIシステムでソフトウェアエンジニアリングツールとして使用する」 は、セクション5.2で非常によく似た言語を使用して、今日純粋関数と呼ぶものを説明します。

メモ化は、プロシージャではなく、真の関数に対してのみ機能します。つまり、関数の結果がその入力パラメーターによって完全かつ確定的に指定されていない場合、メモ化を使用すると誤った結果が得られます。システム全体で関数型プログラミングスタイルの使用を奨励することにより、正常にメモできる関数の数が増加します。

いくつかの命令型言語にも同様のイディオムがあります。たとえば、static const C++の変数は、その値が使用される前に一度だけ初期化され、変更されることはありません。

7
Davislor

それはあなたがそれをどのように行うかに依存します。

通常、人々はなんらかのキャッシュディクショナリを変更してメモ化したいと考えています。これには、同時実行性を心配する必要がある、キャッシュが大きくなりすぎることを心配するなど、不純な変更に関連するすべての問題があります。

ただし、不純なメモリの変更なしでメモすることができます。 1つの例は this answer です。ここで、lengths引数を使用して、メモされた値を外部で追跡します。

Robert Harvey提供のリンク では、遅延評価を使用して副作用を回避しています。

ときどき見られるもう1つの手法は、猫の効果の memoize function のように、IOタイプのコンテキストでメモを不純な副作用として明示的にマークすることです。

この最後の1つは、目的が突然変異を排除するのではなく、単にカプセル化することであるという点を示しています。ほとんどの関数型プログラマは、不純物を明示的にカプセル化するのに「十分に純粋」であると考えています。

真に純粋な関数と区別するための用語が必要な場合は、「可変辞書でメモ化」と言うだけで十分だと思います。それは人々にそれを安全に使用する方法を知らせます。

3
Karl Bielefeldt

通常、リストを返す関数は、ストレージの割り当てを必要とし、(たとえば、純粋でない例外をスローすることにより)失敗する可能性があるため、純粋ではありません。値タイプがあり、リストを制限されたサイズの値タイプとして表すことができる言語では、この問題は発生しない場合があります。このため、あなたの例はおそらく純粋ではありません。

一般に、失敗が発生しない方法でメモ化を実行できる場合(たとえば、メモ化された結果に静的に割り当てられたストレージと、言語がスレッドを許可する場合にそれらへのアクセスを制御するための内部同期によって)、そのような関数を検討するのが妥当です。純粋。

stateモナド を使用すると、副作用のないメモ化を実装できます。

[状態モナド]は基本的には関数S =>(S、A)です。Sは状態を表すタイプで、Aは関数が生成する結果です 猫の状態

あなたの場合、状態はメモされた値か何もありません(つまりHaskell MaybeまたはScala Option[A])。メモ化された値が存在する場合はAとして返され、それ以外の場合はAが計算され、遷移状態と結果の両方として返されます。

0
Samuel