【topotal輪読会】javascript で学ぶ関数型プログラミング 1 章

47
JavaScript で学ぶ 関数型プログラミング 1Topotal 輪読会 髙村成道(@nari_ex2015/1/22

Upload: narimichi-takamura

Post on 18-Jul-2015

1.900 views

Category:

Technology


1 download

TRANSCRIPT

JavaScript で学ぶ関数型プログラミング

1章Topotal 輪読会髙村成道(@nari_ex)

2015/1/22

1.1 JavaScript に関する事実

関数型プログラミング言語をサポート引数として関数を渡すことができる[1,2,3].forEach(alert);// アラート"1"がポップアップ// アラート"2"がポップアップ// アラート"3"がポップアップ→ 引数に渡した関数に配列の要素を順番に渡して実行する

JavaScript の驚異的な柔軟性JavaScript の三銃士

→ apply, arguments, call

apply メソッド• すべての関数において実装されているメソッド

• 引数 1 つ目: this オブジェクト• 引数 2 つ目: 配列fun.apply(thisArg, array)

• 与えられた配列を関数の引数として渡して実行することができる

apply メソッドを用いた例function splat(fun) { return function(array) { // null を指定すると暗黙的にグローバルオブジェクトに変換される return fun.apply(null, array); };}

var addArrayElements = splat(function(x, y) { return x + y });

addArrayElements([1, 2]);//! 3

返り値として関数を返す関数 → 関数型プログラミングの入り口!

arguments 変数• すべての関数内で arguments ローカル変数にアクセス可能• 関数呼び出し時に引数として与えられた値を保持function func() { for (var i = 0, l = arguments.length; i < l; i++) { alert(arguments[i]); }};func(1,2,3)//! 1 //! 2 //! 3

call メソッドapply と同様に、引数に与えられた値を関数に渡して実行第2引数に、配列ではなく個別の引数を受け取るfun.call(thisArg, [, arg1 [, arg2, ...]]);

call メソッドと arguments 変数を用いた例• 任意の引数を渡すことが可能function unsplat(fun) { return function() { return fun.call(null, _.toArray(arguments)); };}var joinElements = unsplat(function(array) { return array.join(' ') });

joinElements(1, 2);//! "1 2"joinElements('-', '$', '/', '!', ':');//! "- $ / ! :"

1.1.1 JavaScript の制限

欠点• 奇妙な仕様• 安全性に欠けている

• グローバルスコープに依存している• 命令型プログラミングをサポート

• 競合するライブラリの山• 多くのモジュールが実装されたがそれぞれに互換性はない

命令型プログラミングと宣言型プログラミング例: 「レジの会計で合計値を計算する」

補足: 命令型プログラミング1. 品物がカゴに残っていれば 2. へ、空であれば 5. へ進む。2. 品物を取り出す。3. 価格を合計値に足す。4. 1. へ戻る。5. 合計値を表示する。→ 順番が異なる場合に破綻する

補足: 宣言型プログラミング1. 合計値とは、カゴの中の品物をあるルールで処理した結果

• ルールA. 品物が0個の場合、合計値は0である。• ルールB. 品物が1個以上の場合、合計値は、カゴの中の品物1つの価格と、残りの品物の合計値を足した値である。

→ 順序関係なし、安全かつ簡潔

欠点はあるがしかし...• 一定の規則を遵守することで一筋の光を見出すことができる

• 安全性が確保される• コードベースのサイズに比例してスケーラビリティが増す• シンプルでわかりやすく、テストしやすいコードになる

→ 本書では、それらを関数型プログラミングによって導く

1.2 関数型プログラミングを始めるために

関数型プログラミングとは値を抽象の単位に変換する関数を使用して行うプログラミングであり、それらを使ってソフトウェアシステムを構築すること

1.2.1 なぜ関数型プログラミングが必要なのか

オブジェクト指向型のシステム• オブジェクトをたくさん組み合わせることでゴールを目指す

• 例: ボタンを押して情報を出すアプリケーション• ShowButton → HiddenPanel → InfoPanel...

オブジェクト同士が互いに関連している→ オブジェクトの変更や追加をしようとしたときに、システムの状態を考慮する必要がある

関数型のシステム• 関数を組み合わせて(合成して)値を変換していく

• 例: markdown → toHTML → postProcess → modifyDOM

• postProcessは、visit,post,addId,genId 関数で構成機能追加時は新しい関数の動作を理解すればよい。データ変換を繰り返し行うことでシステムを構築するのが関数型プログラミング。

1.2.2 抽象単位としての関数

抽象化抽象化の方法として、実装詳細を隠蔽することが挙げられるここでは、エラーや警告を報告するプログラムを考える

function parseAge(age) { if (!_.isString(age)) throw new Error('引数は文字列である必要があります');

var a;

console.log("age を数値に変換しようとしています");

a = parseInt(age, 10);

if (_.isNaN(a)) { console.log(["age を数値に変換できませんでした : ", age].join(''));

a = 0; }

return a;}

parseAge("42");// age を数値に変換しようとしています//! 42

parseAge(42);// Error: 引数は文字列である必要があります

parseAge("frob");// age を数値に変換しようとしています// age を数値に変換できませんでした : frob

//! 0

サンプルコードの問題点• エラー内容を変えたくなった時にそれぞれを出力する行を変更する必要がある

• 情報、警告、エラーを使い分けたい場合も同様

関数に抽象化functon fail(thing) { throw new Error(thing);}

function warn(thing) { console.log(["警告 : ", thing].join(''));

}

function note(thing) { console.log(["情報 : ", thing].join(''));

}

サンプルコード 改function parseAge(age) { if (!_.isString(age)) fail('引数は文字列である必要があります'); ☆ var a;

note("age を数値に変換しようとしています"); ☆

a = parseInt(age, 10);

if (_.isNaN(a)) { warn(["age を数値に変換できませんでした : ", age].join('')); ☆ a = 0; }

return a;}

1.2.3 カプセル化と隠蔽

オブジェクト指向言語の場合データの要素のパッケージングにオブジェクトの境界を使用メソッド経由でデータを操作することでデータをパッケージ化

関数型プログラミングの場合クロージャを使ってデータをカプセル化して隠蔽する

1.2.4 動作単位としての関数

コンパレータ(comparator)• 2つの値を引数に取る

• 1つ目の引数が2つ目よりも小さい場合: 負の値を返す

• 1つ目の引数が2つ目よりも大きい場合: 正の値を返す

• 1つ目の引数が2つ目よりも等しい場合: 0を返す

例: sort()

辞書順なので数字の大小に関係なくソートされる[2, 3, -6, 0, -108, 42].sort();//! [-108, -6, 0, 2, 3, 42][0, -1, -2].sort();//! [-1, -2, 0][2, -3, -1, -6, 0, -108, 42, 10].sort();//! [-1, -108, -6, 0, 10, 2, 3, 42]

comparator 関数を用いた場合function compareLessThanOrEqual(x,y) { if (x < y) return -1; if (x > y) return 1; return 0;}[2, -3, -1, -6, 0, -108, 42, 10].sort(compareLessThanOrEqual);//! [-108, -6, -3, -2, -1, 0, 10, 42]

if (compareLessThanOrEqual(1,1)) cosolelog("同じか小さい");

// なにも表示されない

プレディケート(predicate)常に真偽値を返す関数function lessOrEqual(x, y) { return x<=y;}if (lessOrEqual(1,1)) consolelog("同じか小さい");

// 同じか小さい

[2, -3, -1, -6, 0, -108, 42, 10].sort(lessOrEqual);//! [42, 10, 0, -1, -2, -3, -6, -108] ← 逆順になってる

高階関数によるマッピング関数を引数にとり、新しい関数を生成して返す関数function comparator(pred) { return function(x, y) { if (truthy(pred(x, y))) return -1; else if (truthy(pred(y, x))) return 1; else return 0; };

[100,1,0,10,-1,-2,-1].sort(comparator(lessOrEqual));//! [-2,-1,-1,0,1,10,100]

1.2.5 抽象としてのデータ• 関数は、ある世界からある世界への橋渡しを行う

• 例: 文字列→数値, 文字列→配列, 配列→配列の組...データを抽象的にとらえ、異なるデータへ変換することに意識を集中させる

1.2.6 関数型テイストの JavaScript

有用な関数その1: existy()

• 存在しないことを判定するfunction existy(x) { return x != null };

existy(null);existy(undefined);existy({}.notHere);existy((function(){}());//! false

existy(0)existy(false)//! true

有用な関数その2: truthy()

与えられた値が true とみなされるかどうかを判定するfunction truthy(x) { return (x !== false) && existy(x) };

truthy(false);truthy(undefined);//! false

truthy(0);truthy('');//! true

existy, truthy の応用true なら実行、それ以外なら undefined を返すfunction doWhen(cond, action) { if (truthy(cond)) return action(); else return undefined;}

Array#map

配列のそれぞれの要素に対して引数に与えた関数を実行し、それぞれの実行結果を格納した配列を返す[null, undefined, 1, 2, false].map(existy);//! [false, false, ture, true, true][null, undefined, 1, 2, false].map(truthy);//! [false, false, ture, true, false]

つづき先ほどのコードでは、以下のようなことが行われている• 関数を装った存在の抽象の定義 (existy)

• 既存の関数を使って構築された真値の抽象の定義 (truthy)

• これらの関数を他の関数のパラメータに渡すことによる新たな動作の実現 (truthy, existy と map の組み合わせ)

これこそが関数型プログラミング!!!

1.2.7 実行速度について

関数型プログラミングで記述しても遅くならないよ• 流行りの V8 エンジンはランタイム最適化してくれるぜ• オプティマイザは賢いから気張ってコーディングしないでok

• 関数型プログラミングすると必ず速度低下することはない• 関数型プログラミングはコーディングの時間は短縮する

1.3 Underscore について• なんで使ったの

• いちいち map とか実装してる暇はない• map の実装ではなく概念が説明したいんじゃ!

• Underscore は基本的なライブラリがきちんと揃ってる• 車輪の再発明しても益少なかろうよ

→ Topotal 1系...

1.4 まとめ• 入門的なトピックを扱った• JavaScript アプリケーションを構築する一つの方法が「関数型プログラミング」• 抽象を導き出して関数として構築する• すでに存在する関数を使って、より複雑な抽象を構築する• すでに存在する関数を別の関数に渡すことによって、さらに複雑な抽象を構築する