Math.random()を呼び出す関数は純粋ですか?


112

以下は純粋な関数ですか?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}

私の理解では、純粋な関数はこれらの条件に従います:

  1. パラメータから計算された値を返します
  2. 戻り値を計算する以外は何もしません

この定義が正しい場合、私の関数は純粋な関数ですか?または、純粋な関数を定義するものについての私の理解は間違っていますか?


66
「戻り値を計算する以外は何もしません」しかしMath.random()RNGの状態を変更することを呼び出します。
ポールドレイパー

1
2番目の点は、「(関数に対して)外部の状態を変更しない」というようなものです。そして、最初の部分は、「SAMEパラメータから計算されたSAME値を返す」のように補足する必要があります
。MVCDSは

ランダム性を可能にする半純粋関数の概念はありますか?たとえば、test(a,b)常に同じオブジェクトを返しますRandom(a,b)(異なる具体的な数値を表すことができます)?Randomシンボリックを維持する場合、それは古典的な意味で純粋であり、早期に評価して数値を入れると、おそらく一種の最適化として、関数は依然として「純粋さ」を保持します。
jdm

1
「ランダムな数字を生成する算術的な方法を検討する人は、もちろん、罪の状態にあります。」-ジョンフォンノイマン
Steve Kuo

1
@jdm "semi-pure"のスレッドをたどると、明確に定義されたいくつかの副作用を関数として純粋に関数を考えると、最終的にモナドが発明される可能性があります。ダークサイドへようこそ。> :)
luqui

回答:


185

いいえ、ちがいます。同じ入力が与えられた場合、この関数は異なる値を返します。そして、入力と出力をマップする「テーブル」を作成することはできません。

WikipediaのPure関数の記事から:

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

また、このスレッドで説明されているように、純粋な関数は、入力と出力からのマッピングを表すテーブルに置き換えることができます

この関数を書き換えて純粋な関数に変更する場合は、ランダムな値も引数として渡す必要があります

function test(random, min, max) {
   return random * (max - min) + min;
}

そして、それを次のように呼び出します(例:2と5を最小と最大として):

test( Math.random(), 2, 5)

2
関数の内部で毎回ランダムジェネレータを再シードしてから呼び出すとMath.randomどうなるでしょうか。
cs95 2017年

16
@cᴏʟᴅsᴘᴇᴇᴅそれでも、副作用(将来のMath.random出力を変更する)があります。純粋であるためには、現在のRNG状態を何らかの方法で保存し、再シードし、を呼び出しMath.randomて、以前の状態に復元する必要があります。
LegionMammal978 2017年

2
@cᴏʟᴅsᴘᴇᴇᴅ計算されたすべてのRNGは、ランダム性の偽装に基づいています。その下で何かが実行されている必要があり、それがランダムに表示され、それを説明できず、不純になります。また、おそらくあなたの質問にとってより重要なことですが、Math.randomをシードすることはできません
zfrisch

14
@ LegionMammal978…そしてアトミックにそうします。
wchargin 2017年

2
@cᴏʟᴅswith純粋な関数で動作するRNGを作成する方法はありますが、RNG状態を関数に渡し、関数が置換RNG状態を返すようにする必要があります。これは、Haskell(関数の純粋性を強制する関数型プログラミング言語)が実現する方法ですそれ。
Pharap 2017年

50

あなたの質問に対する単純な答えは、ルール#2に違反するということMath.random()です。

ここでの他の多くの答えは、の存在がMath.random()この機能が純粋ではないことを意味することを指摘しました。しかし、なぜ Math.random()それを使用する関数を汚染するのかを言う価値があると思います。

すべての疑似乱数ジェネレータと同様にMath.random()、「シード」値で始まります。次に、その値を一連の低レベルのビット操作またはその他の操作の開始点として使用し、予測できない(実際にはランダムではない)出力を生成します。

JavaScriptでは、関連するプロセスは実装依存であり、他の多くの言語とは異なり、JavaScriptはシードを選択する方法を提供しません

実装は、乱数生成アルゴリズムの初期シードを選択します。ユーザーが選択またはリセットすることはできません。

このため、この関数は純粋ではありません。JavaScriptは基本的に、ユーザーが制御できない暗黙の関数パラメーターを使用しています。他の場所で計算および保存されたデータからそのパラメーターを読み取るため、定義のルール#2に違反します。

これを純粋な関数にしたい場合は、ここで説明する代替の乱数ジェネレータの1つを使用できます。そのジェネレータを呼び出しますseedable_randomます。1つのパラメーター(シード)を取り、「ランダムな」数値を返します。もちろん、この数はまったくランダムではありません。種によって一意に決定されます。これが純粋な関数である理由です。の出力はseedable_random、入力に基づいて出力を予測することが困難であるという意味で「ランダム」です。

この関数の純粋なバージョンは、3つのパラメーターをとる必要があります。

function test(min, max, seed) {
   return  seedable_random(seed) * (max - min) + min;
}

与えられたトリプルの (min, max, seed)パラメーターに対して、これは常に同じ結果を返します。

あなたがの出力たい場合がありますseedable_randomすることが真にランダムに、あなたは種をランダム化する方法を見つける必要があると思います!また、関数の外部のソースから情報を収集する必要があるため、使用した戦略は必ず純粋ではありません。以下のようmtraceurjpmc26私を思い出させる、これはすべての物理的なアプローチが含まれます。ハードウェア乱数生成器をレンズキャップでWebカメラ大気中のノイズコレクター -でも溶岩ランプ。これらはすべて、関数の外部で計算および保存されたデータの使用を伴います。


8
Math.random()は、その「シード」を読み取るだけでなく、それを変更して、次の呼び出しが別の何かを返すようにします。静的な状態に依存し、変更することは、純粋な関数にとって明らかに悪いことです。
Nate Eldredge 2017年

2
@NateEldredge、かなりそうです!ただし、実装に依存する値を読み取るだけで、純粋さが失われます。たとえば、Python 3ハッシュがプロセス間でどのように安定していないかに気づいたことがありますか?
センダーレ、2017年

2
Math.randomPRNGを使用せず、代わりにハードウェアRNGを使用して実装した場合、この回答はどのように変わりますか?ハードウェアRNGには実際には通常の状態はありませんが、ランダムな値を生成します(したがって、関数の出力は入力に関係なく依然として異なります)。
mtraceur 2017年

@mtraceur、それは正しいです。しかし、答えが大きく変わるとは思いません。実際、これが私が答えで「状態」について話すことに時間を費やさない理由です。ハードウェアRNGからの読み取りは、「計算されて別の場所に保存されたデータ」から読み取ることも意味します。データが計算され、環境とやり取りするコンピューター自体の物理媒体に保存されるだけです。
センダーレ2017年

1
この同じロジックは、Random.orgの大気ノイズのようなより高度なランダム化スキームにも適用されます。+1
jpmc26 2017年

38

純粋な関数は、戻り値が入力値によってのみ決定される関数であり、目に見える副作用はありません。

Math.randomを使用すると、その値を入力値以外のもので決定します。純粋な機能ではありません。

ソース


25

いいえ、出力は提供された入力のみに依存しないため(Math.random()は任意の値を出力できます)、純粋な関数ではありません。純粋な関数は常に同じ入力に対して同じ値を出力する必要があります。

関数が純粋な場合は、同じ入力を持つ複数の呼び出しを最適化し、以前の呼び出しの結果を再利用するのが安全です。

PSは私にとって、そして他の多くの人にとっては、reduxは純粋関数という用語を人気にした。redux docsから直接

あなたが減速機の中で決してしてはいけないこと:

  • 引数を変更します。

  • API呼び出しやルーティング遷移などの副作用を実行します。

  • 純粋ではない関数(Date.now()やMath.random()など)を呼び出します。


3
他の人は素晴らしい答えを提供しましたが、redux docが頭に浮かび、Math.random()がそれらに具体的に言及されたとき、私は抵抗できませんでした:)
Shubhnik Singh

20

数学的な観点から、あなたの署名は

test: <number, number> -> <number>

だが

test: <environment, number, number> -> <environment, number>

がのenvironment結果を提供できる場所Math.random()。そして、実際にランダムな値を生成すると、副作用として環境が変化するため、最初の環境とは異なる新しい環境も返します。

つまり、初期引数(<number, number>パーツ)から取得されない種類の入力が必要な場合は、実行環境を提供する必要があります(この例では、の状態を提供しますMath)。同じことが、I / Oなどの他の回答で言及されている他の事柄にも当てはまります。


アナロジーとして、これがオブジェクト指向プログラミングがどのように表現できるかにも気づくでしょう-私たちが言うなら、例えば

SomeClass something
T result = something.foo(x, y)

その後、実際に使用しています

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>

メソッドが呼び出されたオブジェクトは環境の一部です。そしてなぜSomeClass結果の一部なのか?somethingの状態も変更された可能性があるためです!


7
さらに悪いことに、環境も変化しているので、test: <environment, number, number> -> <environment, number>それはそうであるはずです
ベルギ

1
OOの例がよく似ているかどうかはわかりません。のタイプに基づいてのオーバーロードされた定義にディスパッチする特別なルールを持つa.F(b, c)構文糖と見なすことができます(これは実際にはPythonがそれを表現する方法です)。ただし、両方の表記法ではまだ明示的ですが、非純粋関数の環境はソースコードで言及されていません。F(a, b, c)Faa
IMSoP 2017年


10

この関数がどのように非決定的であるかを正しく指摘する他の回答に加えて、これには副作用もあります。それは、将来の呼び出しがmath.random()別の回答を返すようにすることになります。また、そのプロパティを持たない乱数ジェネレーターは、通常、OSが提供するランダムデバイスから読み取るなど、ある種のI / Oを実行します。どちらも純粋な関数に対して禁止されています。


7

いいえ、そうではありません。結果をまったく理解できないため、このコードはテストできません。このコードをテスト可能にするには、乱数を生成するコンポーネントを抽出する必要があります。

function test(min, max, generator) {
  return  generator() * (max - min) + min;
}

これで、ジェネレータをモックしてコードを適切にテストできます。

const result = test(1, 2, () => 3);
result == 4 //always true

そしてあなたの「生産」コードで:

const result = test(1, 2, Math.random);

1
▲テスト容易性についての考え。少し注意してutil.Random、を受け入れながら繰り返し可能なテストを作成することもできます。テスト実行の開始時にシードして、古い動作を繰り返すか、新しい(ただし繰り返し可能な)実行を行うことができます。マルチスレッドの場合、メインスレッドでこれを実行し、それRandomを使用して反復可能なスレッドローカルRandoms をシードできます。ただし、私が理解しているように、test(int,int,Random)は状態を変更するため、純粋とは見なされませんRandom
PJTraill 2017年

2

以下で問題ありませんか?

return ("" + test(0,1)) + test(0,1);

に等しい

var temp = test(0, 1);
return ("" + temp) + temp;

ご覧のとおり、pureの定義は、その出力が入力以外では変化しない関数です。JavaScriptに関数にタグを付ける方法があり、これを利用する場合、オプティマイザは最初の式を2番目の式に書き換えることができます。

私はこれについて実務経験があります。SQLサーバーは許可さgetdate()newid()、「純粋な」関数であり、オプティマイザは自由に呼び出しを重複排除します。時々これはばかげたことをするでしょう。

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