拙書の延長で、当ブログにて公開している、関数リアクティブプログラミング(FRP)ライブラリ
worldcomponent
について、拙書をレビューすると称する悪意あるQiita記事、『関数型プログラミングに目覚めた!』のレビュー(Day-1)のコメントで、激しい論争が繰り広げれられており、強い関心を持って読み通しました。
この記事では著者/開発者としての見解を提示します。
worldcomponent
がオブジェクトの値の破壊的代入による実装がある、との批判ですが、その通りです。
https://github.com/sakurafunctional/worldcomponent/blob/master/worldcomponent.js
これは、JavaScriptのオブジェクトの値が破壊的代入されたときに、イベントが発生するObject.defineProperties
のset
の特性を利用して実装されているライブラリです。
(このような言語仕様の低層のハックが原理的に関数型であるわけがない)
Object.defineProperties(value,
{
val: //value.val
{
get: function()
{
return state;
},
set: function(x)
{
state = x;
computingF.map(
function(f)
{
f(x);
});
return;
}
}
});
この特性により、オブジェクトの変更をWATCHするためのポーリング実装などは不要で、かなり効率的で無駄のない実用に耐えるFRP機構を実現しています。
すでに当該コメント欄でも複数名の有志により見解が示されていますが、拙書では「ハードウェアモード」の操作として表現もしているとおり、根本的に関数型プログラミングのパラダイムを実現するために、低層のライブラリまで関数型で書く必要性も合理性も一切ありませんし、実際にほとんどのJavaScriptやnode.jsで利用可能な公開されている関数ライブラリはそのライブラリの実装自体(ライブラリのソースコード)は関数型で書かれておらず命令型(もちろん破壊的代入も)で書かれています。worldcomponent
もその一つの関数ライブラリで、利用することで関数型のコードが書けます。
Object.defineProperties
のset
というオブジェクトの値への破壊的代入を「感知」する手法を利用しているので、オブジェクトの参照への破壊的代入をもって実装すると、この機構が機能することはありません。
たとえば、
appear: function(a) {
var f1 = function() {
// value.val = a;
//`Object.defineProperties`の`set`機構に不可欠な「値」の破壊的代入をコメントアウト
var o = {val: a}; //新規オブジェクトの作成
value = o; //オブジェクト参照への破壊的代入
};
return f1;
},
とコードを変更すると、Object.defineProperties
のset
はトリガーされません。つまり、原理的にオブジェクト参照への破壊的代入ではこのライブラリは機能しない設計になっています。
JavaScriptの関数型プログラミングのBestPractice
JavaScriptは基本的に参照の値渡し(参考 JavaScriptはオブジェクトについて参照渡しだなんて、信じない)なので、通常
=
(イコール)
で参照であれ、値であれ、オブジェクトをバックアップをしたと見做すのは危険なので、行うべきではありません。
コメント欄に親切に紹介もされていましたが、このような場合のBestPracticeとしては、これも公開されているundersocreやlodash、Immutable(全部摂書でも紹介しました)の関数ライブラリのオブジェクトのコピー関数を活用して、言語仕様の詳細をライブラリ実装以下に隠蔽してしまい、非関数型的なパラダイムの議論を回避します。
worldcomponent
の利用も関数型のパラダイムにおいて、その実装レベルが問題となることはまったくありません。
また、worldcomponent
の実装について、@Lambada氏により、無駄に複雑なスパゲッティ・プログラムなどと繰り返し激しい誹謗中傷が繰り広げられております。
私としましては、Object.defineProperties
のset
を使ってFRPを効率的に実現するために、無駄がないように極限まで洗練させてコードを公開したつもりですが、すでに@Lambada氏は現段階でworldcomponent
の仕様も含めて十二分に精査されたことでしょうから、もちろんObject.defineProperties
のset
を使わない別の方法でも構わないですし、氏が無駄のない複雑でないコードを提示していただければ助かります。氏のより洗練されたコードで代替した上でnpm
のバージョンを上げて公開するつもりなのでよろしくお願いします。
なぜ、関数型プログラミングのコードについて「オブジェクト参照」だとか「状態オブジェクトの再利用」の話を熱弁しているのか??
@Lambada氏による珍妙な主張、オブジェクト参照によるバックアップであるなら、
過去の状態への巻き戻しや状態オブジェクトの再利用等が容易
ということですが、このような事象はありえないこともコメントで指摘されているとおりです。
これまでの議論でも明らかですが、JavaScriptでは、worldcomponent
、React
のオブジェクト如何に関わらず、対象が何であれ対象の内部実装がわからない限り、自分が何をやったのか知ることはできないので、=
(イコール)で何かをバックアップしたとするのは危険で問題が生じるので行うべきではなく、BestPracticeとして、何らかの適切な関数ライブラリを利用するオブジェクトのコピーを明示的に行います。
だいたい、何故この人は、オブジェクト指向の「オブジェクト参照」だとか「状態オブジェクトの再利用」の話を関数型プログラミングのコードで熱弁されているのでしょうか??
パラダイムの違いに無頓着であるからですし、一義的に私のコードのあら探し、誹謗中傷に熱心なだけで、まったく全体の景色が見えていないからですね。
たとえば、ポジションをニュートラルにして冷静にさせるためにLisp言語にでも置き換えてみると、Lispにはコピーする関数もありますが、「オブジェクト参照の破壊的代入が・・・コピーが・・・」などという議論にはならず、単に新たな値に対して、コピーでもmap
でも関数を用いてイミュータブルに新規に値を明示的に作成するはずです。
この明示的な操作において、容易も困難も何もありません。
意図をもって明示的にやるか、やらないかの違いしかありません。
最後に FRPにおけるオブジェクトのバックアップというナンセンスについて
表層的な観点からは、
oldvalue
(バックアップされたとする古い値)
newvalue
(新規に出現する値)
などで、
過去の状態への巻き戻しや状態オブジェクトの再利用
するということを示唆しているのでしょうが、
このnewvalue
は
「値の破壊的代入」がされているとする、
___totalClicks.now()
と何ら違いはないことは自明です。
また、真に関数型パラダイムの観点からは、
(SICPでも解説されているレベルという意味です
99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その1】「計算機科学のほんとうの基礎」を理解していない。IQ145のJKと同じ事を語るMITの権威とSICPという聖典の権威を借りてマインドコントロールを解いてみよう)
___totalClicks
というのは、イミュータブルなオブジェクトであり、ミュータブルに耐えず変化しているのは、現実世界の我々が意識している「時間」のほうであり、
この我々が常に意識している「現在時間」には、どの瞬間においても常にnow()
で参照透過にイミュータブルな時間軸上の現在時刻のストリームデータにアクセスできる、という設計です。
FRP的設計思想として、現在時間のnow()
が、___totalClicks
というイミュータブルなオブジェクトの唯一の読み取りインターフェイスであり、「オブジェクト」のバックアップなどすべきではありません。
var ___ = worldcomponent;
var ___totalClicks = ___(0);
___.world = ___totalClicks.appear(42); // ___totalClicksの値を42に設定
var ___totalClicksBak = ___totalClicks; // ___totalClicksの「バックアップ」を保存
のようなコードはFRPのコードではないので、書いてはなりません。
真のFRPライブラリとしてのworldcomponent
の設計理念としては、もちろん独立したストリームを別個に宣言しておきます。
var ___ = worldcomponent;
var ___totalClicks = ___(0);
var ___totalClicksBak = ___(___totalClicks.now()); // ___totalClicksの「バックアップ」を保存
___.world = ___totalClicks.appear(42); // ___totalClicksの値を42に設定
//...................
このコードは___totalClicksBak
という命名や、「バックアップ」を保存というコメントの言葉遣いが真のFRP的に徹底的に間違っていますが、対比するためにやむを得ずそのように書いています。
もちろん、FRPの個々に定義されたストリームは勝手に相互に干渉しあうことなどありませんから、@Lambada氏による
このように、___totalClicksのほうしか更新していないのに、___totalClicksBakの値まで変わっています。
というGlitchは起こりようがありませんし、そもそもFRPにおいて、ストリームをオブジェクト参照によるバックアップするのが間違いです。
たとえばReactにしても、オブジェクト参照コピーによるバックアップが、設計上、API上全く想定されていないのと同じことです。宣言型のコードに取り組むときバックアップなどという概念はナンセンスです。このブログでイミュータブルな真に関数型のDBを紹介しました。
10年先を行く斬新な関数型(FRP)データベースについて説明する 99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その3】
そこでは、
すべてのタイムスタンプにデータを保持するのではなくて、
「差分」が発生した瞬間のデータのみを保持する。
というアイデアは、結構使えるんですね。
というか、とっくに広く使われています。
GitHubです。
Gitのようなバージョン管理システムは、データの差分のみを記録していきます。
という真のFRPの実現の具体例の片鱗を紐解きましたが、GitHubが瞬間瞬間のバックアップでなく、すべてのタイムスタンプですべての事象をイミュータブルにパイルアップして記録していくシステムであるのはよく知られていることです。
FRP的設計思想として、現在時間のnow()
以外に、任意の計算可能な、つまり現在時刻より過去という意味ですが、任意の時刻にアクセスできる読み取り可能なインターフェイスを実装しているのが、拙書の最終章Day5で掲載しているtimecomponent
というFRPライブラリであるということになります。
以上のような議論に比較して、
oldvalue
(バックアップされたとする古い値)
newvalue
(新規に出現する値)
というバックアップというのは、いかに非関数型的発想で命令型パラダイムであるか!という事実に気がつけるか気がつけないかがFRPあるいは関数型パラダイムを真に理解できているか否かの違いです。
FRPの議論をするとき、ある論者が、バックアップというナンセンスで批評を始めた時点で、その論者はFRPのことは何も理解していないと断定して構いません。
もちろん拙書を手間暇かけて読解していただいた読者諸氏におかれましては、上記私の意図するところは哲学的根本レベルで伝わっているものと信じます。
0 コメント:
コメントを投稿