『関数型プログラミングに目覚めた!』のレビュー(Day-1)のコメント欄で計算機科学の基礎としての、プログラミングの「時間」要素、そして「時間」を考えるとは即ち、物理学を考えることである、という重要な論点が確認されています。
JavaScriptのDate.now()
をimmutableなストリームと、透過な参照をする関数として考えるとき、誤解されやすいのが、
参照透過性(さんしょうとうかせい、英: Referential transparency)は、計算機言語の概念の一種で、文脈によらず式の値はその構成要素(例えば変数や関数)によってのみ定まるということを言う。 具体的には変数の値は最初に定義した値と常に同じであり、関数は同じ変数を引数として与えられれば同じ値を返すということになる。 当然変数に値を割り当てなおす演算である代入 (Assignment) を行う式は存在しない。 このように参照透過性が成り立っている場合、ある式の値、例えば関数値、変数値についてどこに記憶されている値を参照しているかということは考慮する必要がない、即ち参照について透過的であるといえる。
のうち「文脈によらず」「関数は同じ変数を引数として与えられれば同じ値を返す」という要素だと思います。
@nonstarter氏が感じる「謎」
t1 = Date.now(); (0〜9まで足してコンソールに表示); t2 = Date.now(); console.log(t1 == t2);
と
t1 = Date.now(); (0〜9まで足してコンソールに表示); t2 = t1; console.log(t1 == t2);
とで結果が異なる、というDate.now()の単純明瞭この上ない「参照不透明性」をどうするつもりなのかはまったく謎のママなわけです。
はまさにこの要素の疑念だと読み解けます。
まず、コメント欄で誰も注意していないので、厳しく注意しておきますと、この@nonstarter氏によるコードは関数型プログラミング、あるいは宣言型プログラミングのコードでもなんでもありません。
関数型・宣言型でない理由は単純で、
t1 = Date.now(); // ①
<0〜9まで足してコンソールに表示するための処理コード> ; // ②
t2 = Date.now(); // ③
はコードが
ステップ ①
ステップ ②
ステップ ③
というように、上から下へ時間遷移とともに流れ、値はコードの上下関係、時間の前後に依存する、という命令型の発想を前提として書かれている命令型のコードなので、そもそもが「参照不透明性」だとか関数型のコードとしては議論するのは間違いです。
ステップ ①
と
ステップ ③
では「時間」が違うので、Dateというストリームに参照透明にアクセスする時間関数now()の返り値は異なって当然です。
正しい関数型・宣言型のコードを書くと、
var f = function() {
console.log(Date.now());
};
var dummy1 = setTimeout(f, 0); //ユーザがコードを実行した瞬間に即時実行
var dummy2 = setTimeout(f, 1000); //ユーザがコードを実行した1秒後に実行
こうなります。コードのロジックがコードの上下関係や、時間の前後関係にまったく依存していないことに注意してください。この「非同期」で宣言型のコードでは、ステップ①②③などありません。
<0〜9まで足してコンソールに表示するための処理コード> ;
を関数型で書くならば、その処理の終了のコールバック関数で書きます。@nonstarter氏が書いたような命令の終了の「同期」を待つような命令型のコードで書いてはいけません。
now()の実引数が空だから、「関数は同じ変数を引数として与えられれば同じ値を返す」事を考えるとおかしい!と思うのも、すでにコメント欄で反論されているとおり、間違った考えです。
now()というのは、その特性を表す字面でも明確ですが、時間関数であって、 値はユーザの現在時間に依存するのです。
数学的には、概念的には、コードに表記されずとも暗黙に、ユーザの現在時間が実引数として渡される関数です。
now()という時間関数が返す値はユーザの現在時間に依存するというのは、
@chimetorch氏が引用したコード
http://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmousemove_dom
document.getElementById("myDIV").onmousemove = function() {myFunction(event)};
function myFunction(e) {
var x = e.clientX;
var y = e.clientY;
var coor = "Coordinates: (" + x + "," + y + ")";
document.getElementById("demo").innerHTML = coor;
}
のevent
の値はユーザの現在時間に依存するというのと等価ですし、
elmのコード
import Graphics.Element exposing (..)
import Mouse
main : Signal Element
main = Signal.map show countClick
countClick : Signal Int
countClick = Signal.foldp (\clk count -> count + 1) 0 Mouse.clicks
の「シグナル」のインデックス値はユーザの現在時間に依存するのと等価です。
もちろん、上記elmの「シグナル」同様に、JavaScriptのDateストリームをFRP的に拡張して、
var f = function(now){console.log(now);};
var dummy = Date
.interval(1000) // specifies a 1sec interval between each element
.map(f);
と書けるようにするのは、大変意義あるハックですが、標準で、
var f = function(){console.log(Date.now());};
var dummy = setInterval(f, 1000);
書くほうが楽です。
私が書いた、worldcomponent
も、実用的な観点から、
Date.now()と同様に、now()関数で直接値が参照できるように設計しています。これまで触った数々のFRPライブラリの経験から、この仕様のほうが取り回ししやすいという結論です。
もしくは、
var main = Date.map((now) => {
//now はユーザの現在時刻
//ここにすべてのコードを記述していく
});
と暗黙でなく明示的な表記にすれば、関数型のコードとしてより美しいかもしれません。
しかし、これはただ
明示的な構造の中にnowと書くか、
暗黙的な実引数でDate.now()と書くかの違いにすぎません。
いずれにせよ、@nonstarter氏による命令型のコード以外、私が書いたものも含め上記すべての関数型・宣言型のコードは、
- コードのロジックがコードの上下関係や、時間の前後関係にまったく依存していない
のですが、
- コードの中の値はユーザの現在時間に依存する
ことに注意してください。
前者と後者の区別がつくプログラマは、関数型プログラミング、計算機科学のほんとうの基礎の素養がきちんとありますが、前者と後者の区別がつかず、関数型でなく命令型のコードを書きながら『単純明瞭この上ない「参照不透明性」』などと発言してしまう人は勉強しなおしたほうがよろしいでしょう。
前者については、命令型でない関数型の基礎なので、特にいまさら説明を加える必要はないと思います。
後者については、関数型プログラミングで、時間変化する領域に踏み込むことになるので、多少の説明が必要だと思います。
念の為、もう何度も繰り返しているのですが、この部分を説明していのが、
私の著書
関数型プログラミングに目覚めた! IQ145の女子高生の先輩から受けた特訓5日間
やSICP
99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その1】「計算機科学のほんとうの基礎」を理解していない。IQ145のJKと同じ事を語るMITの権威とSICPという聖典の権威を借りてマインドコントロールを解いてみよう
であるということです。
(後者) コードの中の値はユーザの現在時間に依存する、つまり、関数型プログラミングと古典物理学の密接な関係について解説します。
まず、根本の根本の部分ですが、このように時間の値そのものを取り扱うコードにしても、イベント(シグナル)を使うマウスポインタ、クリックのコードにしても、値は時間変化します。
- コードの中の値はユーザの現在時間に依存する
時間変化するから、「文脈によって変わる」、だから「参照不透明」なミュータブルと考えるのは間違いです。
- コードのロジックがコードの上下関係や、時間の前後関係にまったく依存していない
というのが、「文脈によって変わらない」という意味で、「参照透明」であるということです。
私、そしてSICPの著者が関数型プログラミング、計算機科学の基礎と不可分として使う物理学の用語では、時間発展と言うのですが、
時間発展(じかんはってん)とは、時間が進むことで物理系が変化することである。
古典物理学における時間発展とは、物理量の値が時間によって変化することである。
変化しているのは、時間が進むことで変化する我々の世界の「物理系」「物理量」であることを理解してください。
古典物理学にせよ、関数型プログラミングのコードにせよ、それは数学なので、「参照不透明」に値が変化することなどはありえません。
古典物理学で絶対時間t
が時間が進む(様に見えている)ことで物理系が変化し、物理量の値が時間発展するからといって、絶対時間t
が破壊的代入されている、わけではありませんし、関数型プログラミングにしても全く同様です。
正しい関数型・宣言型のコード
var f = function() {
console.log(Date.now());
};
var dummy1 = setTimeout(f, 0); //ユーザがコードを実行した瞬間に即時実行
var dummy2 = setTimeout(f, 1000); //ユーザがコードを実行した1秒後に実行
は、時間が進むことで変化する我々の世界という「物理系」において不変(immutable)で、参照透過です。
0 コメント:
コメントを投稿