Mais conteúdo relacionado
Semelhante a 【Topotal輪読会】JavaScript で学ぶ関数型プログラミング 2 章 (20)
【Topotal輪読会】JavaScript で学ぶ関数型プログラミング 2 章
- 5. // 変数・配列・オブジェクトのフィールドに代入可能
var f = function() { return 42 },
a = [42, function() { return 42 }],
o = {n: 42, f: function() { return 42 }};
// 任意のタイミングで生成可能
42 + (function(){ return 42 })(); //=> 84
// 引数・返り値に取ることが可能 (恒等関数での例)
function identity(x) { return x };
identity(function() { return 42 }); //=> function()
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 5
- 13. // 命令型プログラミング
var lyrics = [];
for (var bottles = 99; bottles > 0; bottles--) {
lyrics.push(bottles + " 本のビールが残ってる");
lyrics.push(bottles + " 本のビール");
lyrics.push("ひとつ取って、隣に回せ");
if (bottles > 1) {
lyrics.push((bottles - 1) + "本のビールが残ってる");
}
else {
lyrics.push("もうビールは残ってない");
}
}
// この後に 80∼50 本のビールの歌を歌わせたかったら...?
// この部分のテストが書きたくなったら...?
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 13
- 14. // 関数型スタイルへのパラダイムシフト
// "n 本目のビールの歌詞を生成する" 機能を関数へと分割
function lyricSegment(n) {
return _.chain([]) // メソッドチェイン記法
.push(n + " 本のビールが残ってる")
.push(n + " 本のビール")
.push("ひとつ取って、隣に回せ")
.tap(function(lyrics) { // 渡ってきたオブジェクトに関数を適用しオブジェクトを返す
if (n > 1)
lyrics.push((n - 1) + " 本のビールが残ってる");
else
lyrics.push("もうビールは残ってない");
})
.value(); // 値を返す
}
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 14
- 15. // 関数の繋ぎあわせ
function song(start, end, lyricGen) {
return _.reduce(_.range(start,end,-1), // 値を返す
function(acc,n) {
return acc.concat(lyricGen(n)); // 値を返す
}, []);
}
song(99, 0, lyricSegment)
// => ["99 本のビールが残ってる", "99 本のビール", "ひとつ取って、隣に回せ", ... ]
// song(80, 50, lyricSegment) で 80∼50 本のビールの歌が歌える
// 正しく値が返って来ているかのテストも書きやすそう
// 歌詞生成部だけのテストも書けそう
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 15
- 17. 何が良いと感じたか
• 機能別に分けることにより
• 変更に強いプログラムが作れる
• 再利用性が高く同じようなコードを何度も書かなくて良い
• 機能毎のテストが書きやすくなる
• 参照等価にするとここが∼みたいな話は色々あるけど...#7
7
"とりあえずここまでの例で読み取れることのみの感想です
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 17
- 19. 罠
// オブジェクトのフィールド値に関数が存在できる
var a = {name: "a", f: function(){return this}};
// this は a 自身を指す。計画通り。
a.f() // => {name: "a", f: ...}
var bObj = {name: "b", f: function(){return this}},
var bFun = bObj.f;
// !! this は bObj を指さない !! bFun から見た this を指す !!
bFun() // => グローバルオブジェクト (window など)
8
"以降「関数」は単独で存在する関数、「メソッド」はオブジェクトのコンテキストに生成された関数を指す
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 19
- 20. メタプログラミング
• ソースコードを生成するようなプログラムを書くスタイル
• 本書では、以下のような例が紹介されている"9
function Point2D(x, y) { this._x = x; this._y = y; }
function Point3D(x, y, z) { Point2D.call(this, x, y); this._z = z; }
new Point3D(10, -1, 100);
//=> {_x: 10, _y: -1, _z: 100}
• 本書ではほぼ取り扱わないので詳細は省略する
9
"この例があまりメタプログラミングっぽく見えないのは"JavaScript"のデータ構造とプログラム構造が近いから?
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 20
- 22. 作用的プログラミング
• 関数"f: A()"と"f: B()"を次のように使用するプログラミング
• B"を呼び出すことで"A"を実行させる"10
• B(element, A())
• "作用的"という言葉はコンテキストによって異なる意味を持つ
• もう使わないけど意味だけは知っておいてね
10
#と、本書では定義してあるのでこのスライドでもそう定義します。
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 22
- 23. 作用的プログラミングの例
var nums = [1,2,3,4,5];
function doubleAll(array) {
return _.map(array, function(n) { return n*2 });
}
doubleAll(nums); //=> [2, 4, 6, 8, 10]
// ---------------------
function average(array) {
var sum = _.reduce(array, function(a, b) { return a+b });
return sum / _.size(array);
}
average(nums); //=> 3
// ---------------------
function onlyEven(array) {
return _.filter(array, function(n) { return (n%2) === 0; });
}
onlyEven(nums);
//=> [2, 4]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 23
- 24. コレクション中心プログラミング
• 関数型プログラミング
• コレクションに入った多数の値に同じ処理をしやすい
• [1, 2, 3, 4, 5]"や"{a: 1, b: 2}"...
• 本書はこの考え方に則りデータ構造を有効活用する方法を推進
• It#is#be(er#to#have#100#func4ons#operate#on#one#data#structure#than#
10#func4ons#on#10#data#structures.#88#Alan#Perlis
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 24
- 25. コレクション中心プログラミングの例
_.map({a: 1, b: 2}, _.identity); //=> [1,2]
// ---------------------
_.map({a: 1, b: 2}, function(v,k) { return [k,v]; });
//=> [['a', 1], ['b', 2]]
// ---------------------
_.map({a: 1, b: 2}, function(v,k,coll) {
return [k, v, _.keys(coll)];
});
//=> [['a', 1, ['a', 'b']], ['b', 2, ['a', 'b']]]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 25
- 26. 作用的プログラミングのその他の例
// ReduceRight
var nums = [100,2,25];
function div(x,y) { return x/y };
_.reduce(nums, div); //=> 2
_.reduceRight(nums, div); //=> 0.125
// allOf, anyOf; reduce でも同じように記述できる
function allOf(/* funs */) {
return _.reduceRight(arguments, function(truth, f) {
return truth && f();
}, true);
}
function anyOf(/* funs */) {
return _.reduceRight(arguments, function(truth, f) {
return truth || f();
}, false);
}
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 26
- 27. // ReduceRight
// allOf, anyOf の使用例
function T() { return true }
function F() { return false }
allOf(); //=> true
allOf(T, T); //=> true
allOf(T, T, T , T , F); //=> false
anyOf(T, T, F); //=> true
anyOf(F, F, F, F); //=> false
anyOf(); //=> false
• reduce/reduceRight-は演算が結合的でない時に差が発生
• 遅延評価の言語ではもっと大きな違いがある
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 27
- 28. // find, reject, filter, all, any
// find: コレクションとプレディケートを取り真となる要素を返す
_.find(['a', 'b', 3, 'd'], _.isNumber); //=> 3
// find の逆の動作; underscore.js は _.isNumber のようなプレディケート関数を多数用意している
_.reject(['a', 'b', 3, 'd'], _.isNumber); //=> ['a', 'b', 'd']
// 真偽を判定させる関数 complement
function complement(pred) {
return function() { return !pred.apply(null, _.toArray(arguments)); };
}
// _.reject と同じ動作
_.filter(['a', 'b', 3, 'd'], complement(_.isNumber)); //=> ['a', 'b', 'd']
// all: 要素のすべてが true の時 true; any: 要素の1つでも true なら true
_.all([1, 2, 3, 4], _.isNumber); //=> true
_.any([1, 2, 'c', 4], _.isString); //=> true
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 28
- 29. // sortBy, countBy, groupBy
var people = [{name: "Rick", age: 30}, {name: "Jaka", age: 24}];
// コレクションと関数を受け取り関数の基準に応じて並べ替えたコレクションを返す
_.sortBy(people, function(p) { return p.age });
//=> [{name: "Jaka", age: 24}, {name: "Rick", age: 30}]
var albums = [{title: "Sabbath Bloody Sabbath", genre: "Metal"},
{title: "Scientist", genre: "Dub"},
{title: "Undertow", genre: "Metal"}];
// 受け取った関数が返す値をキーとして、その値が等しいオブジェクトを配列に入れて返す
_.groupBy(albums, function(a) { return a.genre });
//=> {Metal:[{title:"Sabbath Bloody Sabbath", genre:"Metal"},
// {title:"Undertow", genre:"Metal"}],
// Dub: [{title:"Scientist", genre:"Dub"}]}
// _.groupBy と同じ動作をするが、キーに対応する値が配列の長さになっている
_.countBy(albums, function(a) { return a.genre }); //=> {Metal: 2, Dub: 1}
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 29
- 30. 作用的関数の定義
// 作用的ではない関数達
// 幾つかの配列を結合する関数
function cat() {
var head = _.first(arguments);
if (existy(head))
return head.concat.apply(head, _.rest(arguments));
else
return [];
}
cat([1,2,3], [4,5], [6,7,8]); //=> [1, 2, 3, 4, 5, 6, 7, 8]
// 要素と配列を引数に取り配列の前に要素を挿入する関数
// これも作用的な関数ではない
function construct(head, tail) {
return cat([head], _.toArray(tail));
}
construct(42, [1,2,3]); //=> [42, 1, 2, 3]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 30
- 31. // 作用的な関数 mapcat
// _.map を使用し fun を受け取り渡されたコレクションの要素全てに対して fun を実行する
// その後 _.map から返された全ての要素を結合する
function mapcat(fun, coll) {
return cat.apply(null, _.map(coll, fun));
}
mapcat(function(e) {
return construct(e, [","]);
}, [1,2,3]);
// => [1, ",", 2, ",", 3, ","]
// construct は要素を配列の先頭に挿入する関数
// 内部的には: cat.apply(null, [[1, ","], [2, ","], [3, ","]])
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 31
- 32. function butLast(coll) {
return _.toArray(coll).slice(0, -1);
}
function interpose (inter, coll) {
return butLast(mapcat(function(e) {
return construct(e, [inter]);
}, coll));
}
interpose(",", [1,2,3]);
//=> [1, ",", 2, ",", 3]
// ([1, 2, 3] => [1, ",", 2, ",", 3, ","] => [1, "," 2, ",", 3])
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 32
- 35. // keys/values は key/value を返す(当然)
var zombie = {name: "Bub", film: "Day of the Dead"};
_.keys(zombie); //=> ["name", "film"]
_.values(zombie); //=> ["Bub", "Day of the Dead"]
// pluck は 指定したキーの要素を全て返す(無い時は undefined)
_.pluck([{title: "Chthon", author: "Anthony"},
{title: "Grendel", author: "Gardner"},
{title: "After Dark"}], 'author');
//=> ["Anthony", "Gardner", undefined]
// pairs は {a: b, c: d} を [[a, b], [c, d]] とする
_.pairs(zombie);
//=> [["name", "Bub"], ["film", "Day of the Dead"]]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 35
- 36. // シーケンシャルな処理を行い、_.object でオブジェクトを再構成する例
_.object(_.map(_.pairs(zombie), function(pair) {
return [pair[0].toUpperCase(), pair[1]];
}));
//=> {FILM: "Day of the Dead", NAME: "Bub"};
// _.invert はキーと値を入れ替える
_.invert(zombie);
//=> {"Bub": "name", "Day of the Dead": "film"}
// JavaScript のキーは必ず文字列になる
_.keys(_.invert({a: 138, b: 9})); //=> ['9', '138']
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 36
- 37. // _.defaults は指定したキーが存在しない場合デフォルト値として追加する
_.pluck(_.map([{title: "Chthon", author: "Anthony"},
{title: "Grendel", author: "Gardner"},
{title: "After Dark"}],
function(obj) {
return _.defaults(obj, {author: "Unknown"})
}), 'author');
//=> ["Anthony", "Gardner", "Unknown"]
// _.pick, _.omit は引数によりオブジェクトの内容をフィルタする (非破壊的操作)
var person = {name: "Romy", token: "j3983ij", password: "tigress"};
var info = _.omit(person, 'token', 'password');
info; //=> {name: "Romy"}
var creds = _.pick(person, 'token', 'password');
//=> {password: "tigress", token: "j3983ij"};
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 37
- 38. // findWhere, where は 2 番目の引数の条件にマッチするオブジェクトを
// 1 番目の引数から探してくる
var library = [{title: "SICP", isbn: "0262010771", ed: 1},
{title: "SICP", isbn: "0262510871", ed: 2},
{title: "Joy of Clojure", isbn: "1935182641", ed: 1}];
// findWhere は最初にマッチしたものだけが返る
_.findWhere(library, {title: "SICP", ed: 2});
//=> {title: "SICP", isbn: "0262510871", ed: 2}
// where はマッチした全ての要素が配列で返る
_.where(library, {title: "SICP"});
//=> [{title: "SICP", isbn: "0262010771", ed: 1},
// {title: "SICP", isbn: "0262510871", ed: 2}]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 38
- 40. テーブルのようなデータ!2
• タイトルを抜き出したテーブル
SELECT title FROM library;
_.pluck(library, 'title'); // ここまでで習った JavaScript での同等の表現
// 但し、オブジェクトから配列に変換されており、型が壊れている ...
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 40
- 41. // _.pick を使用しテーブルデータの抽象型を維持する
function project(table, keys) {
return _.map(table, function(obj) {
return _.pick.apply(null, construct(obj, keys));
});
};
var editionResults = project(library, ['title', 'isbn']);
// => [{isbn: "0262010771", title: "SICP"},
// {isbn: "0262510871", title: "SICP"},
// {isbn: "1935182641", title: "Joy of Clojure"}];
// (_.pick.apply(null, [{...}, 'title', 'isbn']) が要素ごとに実行される)
// データ構造を維持したまま値を返すため、更に project を適用可能
var isbnResults = project(editionResults, ['isbn']);
//=> [{isbn: "0262010771"}, {isbn: "0262510871"}, {isbn: "1935182641"}]
_.pluck(isbnResults, 'isbn'); // 欲しいデータだけを取得する
//=> ["0262010771", "0262510871", "1935182641"]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 41
- 44. // マップデータを指定した通りにリネームする関数
function rename(obj, newNames) {
return _.reduce(newNames, function(o, nu, old) {
if (_.has(obj, old)) {
o[nu] = obj[old]; return o;
}
else
return o;
},
_.omit.apply(null, construct(obj, _.keys(newNames))));
};
rename({a: 1, b: 2}, {'a': 'AAA'}); //=> {AAA: 1, b: 2}
// オブジェクトの再構成に _.reduce を使用している
// これによってマップデータらしさを維持しつつキーの名前を変換している
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 44
- 45. // rename 関数を使用し実装した as 関数
function as(table, newNames) {
return _.map(table, function(obj) {
return rename(obj, newNames); });
};
as(library, {ed: 'edition'});
//=> [{title: "SICP", isbn: "0262010771", edition: 1},
// {title: "SICP", isbn: "0262510871", edition: 2},
// {title: "Joy of Clojure", isbn: "1935182641", edition: 1}]
// as はテーブルデータ型を返すので project, as を同時に適用可能
project(as(library, {ed: 'edition'}), ['edition']);
//=> [{edition: 1}, {edition: 2}, {edition: 1}];
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 45
- 46. // プレディケートを受け取りそれぞれのオブジェクトに対して適用する関数
function restrict(table, pred) {
return _.reduce(table, function(newTable, obj) {
if (truthy(pred(obj)))
return newTable;
else
return _.without(newTable, obj);
}, table);
};
restrict(library, function(book) { return book.ed > 1; });
//=> [{title: "SICP", isbn: "0262510871", ed: 2}]
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 46
- 47. SELECT title, isbn, edition FROM (
SELECT ed AS edition FROM library
) EDS
WHERE edition > 1;
/* SQL での例 */
// 上記の SQL と同等な JavaScript を使った例
restrict(
project(
as(library, {ed: 'edition'}),
['title', 'isbn', 'edition']),
function(book) {
return book.edition > 1;
});
//=> [{title: "SICP", isbn: "0262510871", edition: 2},]
// restrict, as, project は同じテーブル抽象型(オブジェクトの配列)に対して動作する
// これがデータ思考プログラミング
YOSHIKAWA)Ryota)(2015/1/22|2015/2/11))第一回/第二回)Topotal)輪読会 47