脳みそダンプ
#01. 不変性
JavaScriptでは ある程度不変性を意識して書ける。具体的には Array や String の slice
, concat
, map
, filter
、 ...
(スプレッドシンタクス)、Object.assign
、あとJSON.stringify
& JSON.parse
(筋悪?)。ただ、どうしても冗長な書き方になるうえ、うっかりするとすぐにシャロ―コピーになってしまう。JavaScript で不変性を志向した書き方をするのは難しいといわざるを得ない。
解決策の方向性は二つある。生のArray, Objectのまま頑張る Ramda.js 方式と、類似の新たなデータ構造で頑張る Immutable.js 方式。Ramda 方式では、ライブラリとして、Array, Object に対するディープコピー機能を提供するだけでなく、レンズ機能を提供する。Ramda 方式のメリットは、特別なクラスやデータ構造を導入しないでよいという点だろう。一方、Immutable.js 方式は、新たなデータ構造に変換するという手間がある反面、**効率の良いオブジェクトの生成(structural sharing)**が可能だったり、各種メソッドが不変オブジェクトを返すので扱いやすい。
総論でいえば、Immutable.js 方式の方がよい(反論はうけつける)。Ramdaは不変性を保つための手段の選択肢を増やしただけで、前述の危険性を減らすことには貢献しないため。(一人で書くなら Ramda でもよいが、、、あの柔軟性と機能性は魅力)。何より Immutable は特別な使い勝手ではないので、数日も使えば手にすぐ馴染む。
#02. 遅延と無限
JavaScript には、ジェネレータやイテレータプロトコル、オブザーバブルや、関数スコープを持ったクロージャがある。これらを使うことで、必要に迫られるまで処理をしないことは可能だ。しかし、ただ可能というだけで、それが使いやすいかというのは別問題であり、実際なかなか厳しい。
解決策はいくつかあって、Immutable の Seq
、Lodash の chain
、RxJSの Observable
、 Ramda の transduce
などがあげられる。多くの場合これらで間に合うことが多いが、少し要件が複雑になった場合に対応しきれるかといわれると、よどみなくYesと答える自信はない
#03. 再帰
言うまでもないが、JavaScript では末尾再帰の最適化が(現時点では)できない(としたほうが安全)ので、再帰を多用できない。最悪、トランポリンなどで何とかするしかない。
#04. ADT
Maybe
, Either
, IO
, State
などのモナドの実装がすぐに手に入る(Monet.js, Folktale, crocks, ...)うえ、それらの相互運用性を定めた仕様(Fantasy-Land)がある。これにより、自作のモナドも簡単につくれるし、Ramda が提供する lift
, sequence
関数と相互運用することが可能になる。関数型プログラミングの醍醐味は、JavaScript でも十分感じられる。一般の JSer に読めない黒魔術となること請け合いであるが・・・
#05. 関数合成・ポイントフリー
JavaScript では関数は第一級オブジェクトなので、高階関数・コンビネータは可能。lodash, Ramda, crocks のいづれかのライブラリを入れておけばすぐに使え、関数合成によるソフトウェアの組み立てができる。JavaScript のアローによる匿名関数の簡略な記法も一役買っている。
さいごに
JavaScript で関数型にプログラミングをする際のあれこれをダンプした。意見・感想・議論は非常にウェルカムです