JavaScript*で学ぶ関数型プログラミング*5章
関数を組み立てる関数
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 1
本スライドの大まかな流れ
1. 関数合成の基礎
2. カリー化
3. 部分適用
4. まとめ
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 2
本章の目的
関数の部品を集めて、より豊かな機能を持った関数を「合成す
る」様々な方法を探る
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 3
5.1$関数合成の基礎
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 4
関数合成とは
• 関数と関数を組み合わせて新しい関数を生成すること
• 以下の例では"f"と"g"を組み合わせた関数"g∘f"を表す
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 5
invoker(関数を振り返る
• 4#章(p107)で紹介された
• 関数を生成して返す
• オブジェクトをターゲットにしてメソッド呼び出しを行う
• オブジェクトが該当メソッドを持っていない場合は#
undefined#を返す
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 6
function invoker (NAME, METHOD) {
return function(target /* args ... */) {
if (!existy(target)) fail("Must provide a target");
var targetMethod = target[NAME];
var args = _.rest(arguments);
return doWhen((existy(targetMethod) && METHOD === targetMethod), function() {
return targetMethod.apply(target, args);
});
};
};
var rev = invoker('reverse', Array.prototype.reverse);
_.map([[1,2,3]], rev);
//=> [[3,2,1]]
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 7
ポリモーフィックな関数
• ポリモーフィックな関数とは
• 与えられた引数によって異なる動作を行う
• 1つ以上の関数を引数に取り、それらの関数を#undefined#
以外の値が返されるまで順番に呼び出す
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 8
construct(関数の振り返り
// 2 章(p55)にて紹介された cat, construct の振り返り
function cat() {
var head = _.first(arguments);
if (existy(head))
return head.concat.apply(head, _.rest(arguments));
else
return [];
}
// 要素と配列を引数に取り、配列の前に要素を挿入する関数
function construct(head, tail) {
return cat([head], _.toArray(tail));
}
construct(42, [1,2,3]);
//=> [42, 1, 2, 3]
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 9
function dispatch(/* funs */) {
var funs = _.toArray(arguments);
var size = funs.length;
return function(target /*, args */) {
var ret = undefined;
var args = _.rest(arguments);
for (var funIndex = 0; funIndex < size; funIndex++) {
var fun = funs[funIndex];
ret = fun.apply(fun, construct(target, args));
if (existy(ret)) return ret;
}
return ret;
};
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 10
dispatch)関数が行っていること
1. 関数が格納された配列を走査
2. 指定したオブジェクトでそれぞれの関数を呼び出す
3. 最初に指定された実際(existy()%が%true)の値を返す
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 11
配列!or!文字列を文字列に変換する関数をつくる
• 普通に書いたら下記のようになりがち
• 文字列"or"配列を引数に取る関数を定義
• 引数に与えられたデータの型や妥当性を判定
• if'else"ブロックでそれぞれのデータ型の"toString"メソッド
呼び出し
→"invoker"関数と"dispatch"関数を用いると単純化できる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 12
invoker!関数と!dispatch!関数を同時に利用
var str = dispatch(invoker('toString', Array.prototype.toString),
invoker('toString', String.prototype.toString));
str("a");
//=> "a"
str(_.range(10));
//=> "0,1,2,3,4,5,6,7,8,9"
→"ポリモーフィックな関数であることがわかる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 13
dispatch!関数における決まりごとの確認
• 下記条件が満たされるまで関数を順番に実行する
• 与えられた配列に格納された関数がなくなる
• 実行した関数が正常な値を返す
※!invoker!関数の仕様に依存するわけではない
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 14
文字列を逆順ソートする関数
function stringReverse(s) {
if (!_.isString(s)) return undefined;
return s.split('').reverse().join('');
}
stringReverse('abc');
//=> "cba"
stringReverse(1);
//=> undefined
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 15
逆順ソートを行うポリモーフィックな関数
stringReverse!と!Array#reverse!を組み合わせる
var polyrev = dispatch(invoker('reverse', Array.prototype.reverse),
stringReverse);
polyrev([1,2,3]);
//=> [3, 2, 1]
polyrev('abc');
//=> "cba"
→"異なるデータ型を扱うことができた
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 16
終了関数との合成
• 下記の例では"always"が終了関数
var polyrev = dispatch(invoker('reverse', Array.prototype.reverse),
stringReverse);
// dispatch 関数によって生成された関数も dispatch 関数の引数になることができる
var sillyReverse = dispatch(polyrev, always(42));
sillyReverse([1,2,3]);
//=> [3, 2, 1]
sillyReverse("abc");
//=> "cba"
sillyReverse(1000000);
//=> 42
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 17
switch!文!による手動ディスパッチを!
dispatch!関数を用いて簡潔にする
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 18
function performCommandHardcoded(command) {
var result;
switch (command.type)
{
case 'notify':
result = notify(command.message);
break;
case 'join':
result = changeView(command.target);
break;
default:
alert(command.type);
}
return result;
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 19
performCommandHardcoded!関数の利用例
引数に渡された!command!オブジェクトのフィールドを参照し、
コマンド文字列の内容によって異なる関数を実行する
performCommandHardcoded({type:'notify', message: 'hi!'});
// notify 関数を実行
performCommandHardcoded({type:'join', message: 'waiting-room'});
// changeView 関数を実行
performCommandHardcoded({type:'wat'});
// alert 関数を実行
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 20
dispatch!関数の利用
function isa(type, action) {
return function(obj) {
if (type === obj.type)
return action(obj);
};
}
var performCommand = dispatch(
isa('notify', function(obj) { return notify(obj.message); }),
isa('join', function(obj) { return changeView(obj.target); }),
function(obj) { alert(obj.type); }
);
performCommand!関数に渡す関数の注意点を考えてみよう
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 21
拡張性の比較
• performCommandHardcoded"関数の場合
• 拡張する場合、switch"文の内部を変更する必要がある
• performCommand"関数の場合
• 別の"dispatch"関数でラッピングするだけで済む
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 22
performCommand!関数の拡張例!その!1
新たなコマンド!kill!を追加する例
→!performCommand!関数をラップすることで拡張する
// performCommand の拡張例
var performAdminCommand = dispatch(
isa('kill', function(obj) { return shutdown(obj.hostname); }),
performCommand
);
performCommand({type: 'kill', hostname: 'localhost'});
// シャットダウン
performCommand({type: 'fail'});
// alert を実行
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 23
performCommand!関数の拡張例!その!2
join!コマンドを制限する例
→!既存コマンドをオーバーライドする。
var performTrialUserCommand = dispatch(
isa('join', function(obj) { alert("Cannot join until approved") }),
performCommand);
performTrialUserCommand({type: 'join', target: 'foo'});
// 拒否メッセージが入った alert を実行
performTrialUserCommand({type: 'notify', message: 'Hi new user'});
// notify 関数を実行
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 24
関数合成のまとめ
• 関数合成とは、関数を組み合わせて新しい関数を生成すること
• これまで説明した以下の内容が関数合成の本質
• 既知の方法で既存のパーツを使うことによって新たな動作を
組み立てる
• 上記で組み立てた新たな動作も後にパーツとして利用できる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 25
5.2$変異は低レイヤーでの操作
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 26
変数の変異は気にする必要がない
• 関数型プログラミングにおいて関数は抽象の最小単位
• 関数が変数の境界をつくるのでローカル変数の状態変更は関数
の外部に漏れることはない
• 変数の変異は低レイヤーにおける操作として意識の外に置く
べき
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 27
命令型プログラミングとの比較
// 命令型プログラミング
var result = 0;
for(var n = 1; n <= 10; n++) {
result = result + n;
}
// 関数型プログラミング
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var plus = function(a, b) {
return (a + b);
};
var result = _.reduce(numbers, plus);
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 28
使うべき道具を理解しよう
• 本書は関数型プログラミングの美徳を熱く説くものではない
• ライブラリの特性や実行速度などの都合により、命令型プログ
ラミングで実装すべき場面もある
• 問題とその解決策を理解する力とそこで使える引き出しを持っ
ていることが、最善のソリューションにつながる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 29
5.3$カリー化
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 30
カリー化とは
• 複数の引数を取る関数を、1#つの引数のみを取る関数のチェー
ンに変換する処理
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 31
数学的に捉えてみる
!は!2!引数関数であり、左から順に引数として! ,! !を受け取る
これを左からカリー化すると...
このとき、関数! !は!関数! !はカリー化された関数という。
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 32
関数合成の基礎を振り返る
!のとき! !ならば! !が成り立つ!
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 33
カリー化(右から)を図示
※!本書では右の引数からカリー化を行う
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 34
カリー化の具体例
function leftCurryDiv(n) {
return function(d) {
return n/d;
};
}
function rightCurryDiv(d) {
return function(n) {
return n/d;
};
}
var divide10By = leftCurryDiv(10);
divide10By(2)
//=> 5
var divideBy10 = rightCurryDiv(10);
divideBy10(2)
//=> 0.2
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 35
5.3.2%自動的にパラメータをカリー化
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 36
固定された関数を返す高階関数!curry
• divide10By"と"divideBy10"は手動だった
• 今回は引数を"1"つだけ取るように固定された関数を返す関数で
カリー化を行う
function curry(fun) {
return function(arg) {
return fun(arg);
};
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 37
parseInt
• parseInt:#文字列を引数にとり、それを数値に変換する
• 第#1#引数:#文字列
• 第#2#引数:#底
parseInt('11');
//=> 11
parseInt('11', 2);
//=> 3
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 38
parseInt!+!Array#map!で発生する問題
• それぞれの配列要素に対して引数に与えられた関数を実行
• 実行時、要素・インデックス・元の配列が与えられる
• 引数を"1"つだけ取るように矯正すれば問題ない
> ['11','11','11','11'].map(parseInt);
//=> [ 11, NaN, 3, 4 ]
> ['11','11','11','11'].map(curry(parseInt));
//=> [ 11, 11, 11, 11 ]
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 39
2"つのパラメータをカリー化
function curry2(fun) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg);
};
};
}
var parseBinaryString = curry2(parseInt)(2);
parseBinaryString("111");
//=> 7
parseBinaryString("10");
//=> 2
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 40
コラム:"なぜ右からカリー化?
• 右のほうにある引数は専門化のための
オプションであることが多い
• parseInt"もこれに当てはまる
• これを固定化(または無視)すること
で関数の動作をコントロールしやすく
なる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 41
5.3.2.1&カリー化を利用して新しい関数を生成する
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 42
var plays = [{artist: "Burial", track: "Archangel"},
{artist: "Ben Frost", track: "Stomp"},
{artist: "Ben Frost", track: "Stomp"},
{artist: "Burial", track: "Archangel"},
{artist: "Emeralds", track: "Snores"},
{artist: "Burial", track: "Archangel"}];
_.countBy(plays, function(song) {
return [song.artist, song.track].join(" - ");
});
//=> {"Ben Frost - Stomp": 2,
// "Burial - Archangel": 3,
// "Emeralds - Snores": 1}
function songToString(song) {
return [song.artist, song.track].join(" - ");
}
var songCount = curry2(_.countBy)(songToString);
songCount(plays);
//=> {"Ben Frost - Stomp": 2,
// "Burial - Archangel": 3,
// "Emeralds - Snores": 1}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 43
5.3.2.2%3%段階%カリー化で%HTML%カラーコードビルダーを実装
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 44
_.uniq
• 配列から重複要素を取り除いた新しい配列を返す
• _.uniq(array, [isSorted], [iterator])
• isSorted"はすでに配列がソート済みの場合に用いる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 45
3"段階のカリー化
function curry3(fun) {
return function(last) {
return function(middle) {
return function(first) {
return fun(first, middle, last);
};
};
};
};
var songsPlayed = curry3(_.uniq)(false)(songToString);
songsPlayed(plays);
//=> [{artist: "Burial", track: "Archangel"},
// {artist: "Ben Frost", track: "Stomp"},
// {artist: "Emeralds", track: "Snores"}]
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 46
HTML%カラーコードを生成する関数
function toHex(n) {
var hex = n.toString(16);
return (hex.length < 2) ? [0, hex].join(''): hex;
}
function rgbToHexString(r, g, b) {
return ['#', toHex(r), toHex(g), toHex(b)].join('');
}
rgbToHexString(255, 255, 255);
//=> "#ffffff"
var blueGreenish = curry3(rgbToHexString)(255)(200);
blueGreenish(0);
//=> "#00c8ff"
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 47
5.3.3$「流暢な」APIのためのカリー化
• Haskell(では、関数はデフォルトでカリー化されている
• 一方、JavaScript(ではそうではない。そのため、API(やドキュメ
ントを用意する必要がある
• カリー化を用いるかどうかの一般的なルールは「API(が高階関
数を活用するか」
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 48
5.3.4%JavaScript%におけるカリー化のデメリット
• 任意の段階までカリー化する関数は実用的ではない
• Haskell(や(Shen(では多段カリー化を有効活用できる(API(がある
• JavaScript(では一般的にカリー化は不利に働き、混乱を招く
• 可変数引数が許可されているため
• カリー化よりも任意の深さまでの部分適用がより一般的に使わ
れる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 49
5.4$部分適用
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 50
部分適用とは
• 関数を"任意"の数だけ引数を指定して部分的に実行する
• 部分適用された関数は残りの引数を与えると即時実行される
• f(a,b,c)"→"g(a,b)
• a,b"を付与すると即時実行
• カリー化は"h(a)"のように引数を1つにする
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 51
5.4.1%1つ・2つの既知の引数を部分適用
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 52
1つの既知の引数を部分適用
function div(n, d) { return n / d }
function partial1(fun, arg1) {
return function(/* args */) {
var args = construct(arg1, arguments);
return fun.apply(fun, args);
};
}
var over10Part1 = partial1(div, 10);
over10Part1(5);
//=> 2
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 53
2つの既知の引数を部分適用
function partial2(fun, arg1, arg2) {
return function(/* args */) {
var args = cat([arg1, arg2], arguments);
return fun.apply(fun, args);
};
}
var div10By2 = partial2(div, 10, 2);
div10By2()
//=> 5
→"1つか2つの引数の部分適用はよくある。
任意の数の引数を予め適用できればさらに便利になる。
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 54
5.4.2%任意の数の引数を部分適用
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 55
function partial(fun /*, 任意の数の引数*/) {
var pargs = _.rest(arguments);
return function(/* arguments */) {
var args = cat(pargs, _.toArray(arguments));
return fun.apply(fun, args);
};
}
var over10Partial = partial(div, 10);
over10Partial(2);
//=> 5
var div10By2By4By5000Partial = partial(div, 10, 2, 4, 5000);
div10By2By4By5000Partial();
//=> 5
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 56
5.4.3%部分適用の実用例:%事前条件
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 57
validator!関数を思い出してみよう
• 検証用のプレディケート関数を引数に取る
• エラーが発生した場合のエラーメッセージを関数のオブジェク
トフィールドに格納して返す関数
function validator(message, fun) {
var f = function(/* args */) {
return fun.apply(fun, arguments);
};
f['message'] = message;
return f;
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 58
validator!関数の利用例
var zero = validator("0 ではいけません", function(n) { return 0 === n; });
var number = validator(" 引数は数値である必要があります", _.isNumber);
function sqr(n) {
if (!number(n)) throw new Error(number.message);
if (zero(n)) throw new Error(zero.message);
return n * n;
}
sqr(10);
//=> 100
sqr(0);
// Error: 0 ではいけません
sqr('');
// Error: 引数は数値である必要があります
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 59
事前条件と事後条件
• 事前条件
• 関数の呼び出し元の保証
• 例示したような入力データ検証(zero,#number)
• 事後条件
• 事前条件が満たされたと想定した場合の、関数呼び出しの結
果に対する保証
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 60
事前条件と計算の骨子を区別する
• zero"と"number"という2つの事前条件は計算自体の骨子に関係
しない
• 実行部の動作保証を行うだけなので分離すべき
→事前条件と計算の骨子を分離し、そのあとで新たな関数を用い
てそれらを部分適用を行うことで結びつける
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 61
// 具体的な事前条件を引数に取る
function condition1(/* validators */) {
var validators = _.toArray(arguments);
return function(fun, arg) {
var errors = mapcat(function(isValid) {
return isValid(arg) ? [] : [isValid.message];
}, validators);
if (!_.isEmpty(errors))
throw new Error(errors.join(", "));
return fun(arg);
};
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 62
condi&on1(の利用例
var sqrPre = condition1(
validator("0 ではいけません", complement(zero)),
validator("引数は数値である必要があります", _.isNumber));
sqrPre(_.identity, 10);
//=> 10
sqrPre(_.identity, '');
// Error: 引数は数値である必要があります
sqrPre(_.identity, 0);
// Error: 0 ではいけません
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 63
安全でない!sqr!関数の例
function uncheckedSqr(n) { return n * n };
uncheckedSqr('');
//=> 0
• JavaScript+は演算時に空の文字列を+0+に自動変換する
• 空の文字列の2乗が+0+であることを許容すべきではない
→"これを解決するために"validator,"partial1,"condition1,"
sqrPre"を組み合わせる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 64
おさらい!1
// エラーが発生した場合のエラーメッセージを関数のオブジェクトフィールドに格納して返す
function validator(message, fun) {
var f = function(/* args */) {
return fun.apply(fun, arguments);
};
f['message'] = message;
return f;
}
// 1つの既知の引数を部分適用
function partial1(fun, arg1) {
return function(/* args */) {
var args = construct(arg1, arguments);
return fun.apply(fun, args);
};
}
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 65
おさらい!2
// 事前条件と計算の骨子を分離する
function condition1(/* validators */) {
var validators = _.toArray(arguments);
return function(fun, arg) {
var errors = mapcat(function(isValid) {
return isValid(arg) ? [] : [isValid.message];
}, validators);
if (!_.isEmpty(errors))
throw new Error(errors.join(", "));
return fun(arg);
};
}
// 事前条件と計算の骨子を結びつける
var sqrPre = condition1(
validator("0 ではいけません", complement(zero)),
validator("引数は数値である必要があります", _.isNumber));
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 66
引数の妥当性チェックを計算から分離する
var checkedSqr = partial1(sqrPre, uncheckedSqr);
checkedSqr(10);
//=> 100
checkedSqr('');
// Error: 引数は数値である必要があります
checkedSqr(0);
// Error: 0 ではいけません
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 67
更に追加の検証項目を加える
var sillySquare = partial1(
condition1(validator("偶数を入力してください", isEven)), checkedSqr);
sillySquare(10);
//=> 100
sillySquare(11);
// Error: 偶数を入力してください
sillySquare('');
// Error: 引数は数値である必要があります
sillySquare(0);
// Error: 0 ではいけません
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 68
関数の生成時のカリー化および部分適用の制約
• 「1#つ以上の数の引数を専門化することによって合成する」と
いう共通の制約
• 引数と戻り値の関係を関数合成に持ち込みたい場合がある
→"関数を並べて端から端までつなぎ合わせる"_.compose"関数を
紹介する
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 69
5.5#並べた関数を端から端まで#compose#関数
でつなぎ合わせる
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 70
関数のつなぎあわせ
function isntString(str) {
return !_.isString(str);
}
isntString(1);
//=> true
// compose 関数によるつなぎあわせ(右から左に実行される)
var isntString = _.compose(function(x) { return !x }, _.isString);
isntString([]);
//=> true
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 71
5.5.1$合成を使った事前条件と事後条件
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 72
前節のおさらいと本節の目標
• 前節のおさらい
• 引数が前提に準拠するかを確認してから二乗する関数"
checkedSqr"を組み立てた
• 本節の目標
• 事後条件(関数呼び出しの結果に対する保証)を"
checkedSqr"に追加する
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 73
事後条件の定義
var sqrPost = condition1(
validator("結果は数値である必要があります", _.isNumber),
validator("結果はゼロではない必要があります", complement(zero)),
validator("結果は正の数である必要があります", greaterThan(0)));
sqrPost(_.identity, 0);
// Error: 結果はゼロではない必要があります, 結果は正の数である必要があります
sqrPost(_.identity, -1);
// Error: 結果は正の数である必要があります
sqrPost(_.identity, '');
// 結果は数値である必要があります, 結果は正の数である必要があります
sqrPost(_.identity, 100);
//=> 100
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 74
事後条件を既存の関数に追加する
_.compose!を接着剤として利用する
var megaCheckedSqr = _.compose(partial(sqrPost, _.identity), checkedSqr);
// 事前条件の検証時エラー
megaCheckedSqr(10);
//=> 100
megaCheckedSqr(0);
// Error: 0 ではいけません
// 事後条件の検証時エラー
megaCheckedSqr(NaN);
// Error: 結果は正の数である必要があります
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 75
事後条件でのエラーは常に自らの失敗
• 事後条件の検証時エラーは以下のようなときに発生する
• 事前条件に漏れがある
• 事後条件が厳しすぎる
• 内部ロジックに不具合がある
→"自らの失敗により発生することがほとんどである
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 76
5.6$まとめ
• 一般的もしくは特化した目的のいずれでも、既存の関数から新
しい関数を合成することができるという考え方を実践した
• カリー化と部分適用の説明とそれらの具体的な利用方法を紹介
した
• conditionや_.compose"などの関数を用いて、事前条件と事
後条件を既存の関数に追加する具体例を示した
TAKAMURA'Narimichi'/'第'4'回'Topotal'輪読会@2015/03/18 77

【Topotal輪読会】JavaScript で学ぶ関数型プログラミング 5 章