Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ECMAScript6による関数型プログラミング

9,313 views

Published on

2014/10/29のES6+カジュアルトークで発表した資料です。

Published in: Engineering
  • @k_matsuzaki さんにご指摘していただいたように、階乗の再帰計算を末尾呼び出し最適化が正しくなされるように修正しました。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

ECMAScript6による関数型プログラミング

  1. 1. ECMAScript6による 関数型プログラミング 株式会社トライフォート 安田裕介
  2. 2. 自己紹介 • 名前:安田裕介 • Trifortに今年入社の新卒1年生 • Webフロントエンジニアやってます • JavaScript, Scala, C++が好き • GitHubアカウント: TanUkkii007
  3. 3. 関数型プログラミングとは? 副作用を排除し関数オブジェクトを駆使する プログラミングパラダイム 関数型プログラミングの2大構成要素 • 第一級オブジェクトとしての関数 • イミュータビリティ(不変性) 拡張性と保守性の高いコードを書く手法として 近年注目を集めている
  4. 4. JavaScriptと 関数型プログラミングの関係 JavaScriptは関数型プログラミング言語である Schemeの第一級関数オブジェクトを受け継いだ言語 ECMAScript6は関数型の以下の機能をも 可能にする 1. 変更不可能な変数の宣言 2. パターンマッチ 3. 再帰による繰り返し処理 4. 不変なデータ構造
  5. 5. 1.変更不可能な変数の宣言 • 関数型プログラミングでは値の変更を行わない • 変数への再代入を行わない
  6. 6. const宣言子 再代入できない変数を宣言する 1 "use strict"; 2 const foo = "foo"; 3 4 foo = “bar”; 5 //TypeError: foo is read-only ※strictモードの場合、再代入しようとするとTypeErrorとなる ※strictモードでない場合、再代入は暗黙に失敗する ※letと同様ブロックスコープをもつ
  7. 7. 2.パターンマッチ • 関数型プログラミングではパターンマッチによる値 の取り出しを行う • パターンマッチにより代入などの副作用を減らすこ とができる
  8. 8. 分割代入(デストラクチャリング) 配列やオブジェクトからパターンによって値を抽出する 配列パターンによる抽出 1 var [a,b] = [1,2];! 2 console.log(a, b);! 3 // 1 2! 4 ! 5 [b, a] = [a,b];! 6 ! 7! 8 var {name: name, family: {sister: sister}} ! 分割代入による値の交換 オブジェクトパターンによる抽出 ! ! ! ! ! ! = {name: 'John Doe', family: {sister: 1}}! 9 console.log(name, sister);! 10 // "John Doe" 1 ※for in/ofループや関数の引数でも使えます
  9. 9. 3.再帰による繰り返し処理 • 繰り返し処理の方法として再帰とループの2つの方法がある • 再帰の方が代入などの副作用がなく、短く書ける • 関数型プログラミングでは繰り返し処理に再帰を使う • 末尾呼び出し最適化により再帰でのスタックオーバーフローを回避 する
  10. 10. 末尾呼び出し最適化 関数呼び出しが末尾呼び出しかどうかを判定し、 末尾呼び出しの場合、最適化する call(call(call(call()))) • 関数は呼び出し元に戻るた め、その位置を記憶する • でもそれが関数本体の末尾 なら、戻る必要はない そこで末尾呼び出し最適化がおきる
  11. 11. 例)階乗の計算 1 // 再帰による階乗計算! 2 function factorial1(n) {! 3 if (n === 0)! 4 return 1;! 5 return n * factorial1(n - 1);! 6 }! 7 ! 8 //ループによる階乗計算! 9 function factorial2(n) {! 10 var result = 1;! 11 for (var i = 1; i <= n; ++i) {! 12 result *= i;! 13 }! 14 return result;! 15 }! 16 ! 17 factorial1(100000);! 18 // too much recursion! 19 factorial2(100000);! 20 // Infinity @k_matsuzaki さんに指摘して もらいました。これでは最適化 されません。正しくは↓ ←これが末尾呼び出し function factorial1(n, acc) { if (n == 0) return acc; return factorial1(n - 1, n * acc); 末尾呼び出し最適化が実装されれば 解決される!! } ←再帰ではスタックオーバーフロー ←ループではInfinityではあるが成功 factorial1(100000, 1);
  12. 12. 4.不変なデータ構造 • 関数型プログラミングにおける配列やリストなどの データ構造は不変であり、自身の値を変更しない
  13. 13. プロキシ 既存のオブジェクトをラップし、その一部の内部 メソッドをECMAScriptコードで実装して挙動を 変えることを可能にするオブジェクト プロキシオブジェクトの作り方 ! ! ! new Proxy(target, { get: //proxy[name] set: //proxy[name] = val apply: //proxy() construct: //new Proxy() deleteProperty: //delete proxy[name] }) ラップするターゲットオブジェクト 内部メソッドに実装を与えるハンドラーオブジェクト がおきたときの処理を 定義できる ※ハンドラーの各メソッドをトラップという ※トラップは全部で14個ある  ※定義されていないトラップ ではデフォルトの挙動が用いられる ※apply, constructトラップは関数がターゲットとなるときのみ有効
  14. 14. 例)プロキシによる イミュータブルな配列 関数型プログラミングでは値を変更しない ES6のプロキシを使って不変な配列を作ってみよう 方針 JavaScriptの配列には自身を変更する破壊メソッドがある pop, push, reverse, shift, sort, splice, unshift 破壊メソッドにsliceを挟むことで非破壊メソッドに変える
  15. 15. 通常の配列と同様 Arrayコンストラクタで 配列を作れるようにする 1 var immutable = { 2 Array: function(...array) { 3 return immutable.createArray(array); 4 }, 5 createArray: function(array = []) { 6 return new Proxy(array, { 7 get: function(target, name, receiver) { 8 var mutator 9 = immutable.mutators.filter(x => x === name)[0]; 10 if (mutator) { 11 return (...args) => { 12 var copy = target.slice(); 13 copy[mutator].apply(copy, args); 14 return immutable.createArray(copy); 15 }; 16 } else { 17 return target[name]; 18 } 19 } 20 }); 破壊メソッドの判定用の配列 21 }, 22 mutators: ["pop","push","reverse","shift","sort","splice","unshift"] 23 };
  16. 16. 1 var immutable = { 2 Array: function(...array) { プロキシを 3 return immutable.createArray(array); 4 }, 5 createArray: function(array = []) { 6 return new Proxy(array, { 7 get: function(target, name, receiver) { 8 var mutator 9 = immutable.mutators.filter(x => x === name)[0]; 10 if (mutator) { 11 return (...args) => { 12 var copy = target.slice(); 13 copy[mutator].apply(copy, args); 14 return immutable.createArray(copy); 15 }; 16 } else { 17 return target[name]; 18 } 19 } 20 }); 21 }, 22 mutators: ["pop","push","reverse","shift","sort","splice","unshift"] 23 }; 作成しているのはここ
  17. 17. ! ! 5 createArray: function(array = []) { 6 return new Proxy(array, { ターゲットに配列を使用 7 get: function(target, name, receiver) { 8 getトラップ var mutator 9 を定義 = immutable.mutators.filter(x => x === name)[0]; 10 if (mutator) { 11 return (...args) => { 12 破壊メソッドなら var copy = target.slice(); 13 sliceを挟む関数を返す copy[mutator].apply(copy, args); 14 return immutable.createArray(copy); 15 }; 16 } else { コピーした配列で 17 return target[name]; プロキシを再作成 18 } 19 } 20 }); 21 }
  18. 18. プロキシで作った不変配列は 配列とどう違うのか? ! 1 var array = new immutable.Array(1,2,3); 2 var nativeArray = new Array(1,2,3); 3 4 array[array.length - 1]; //3 5 nativeArray[nativeArray.length - 1]; //3 6 7 for (var v of array) console.log(v); //1 2 3 8 for (var v of nativeArray) console.log(v); //1 2 3 9 10 11 var result = array.push(4).reverse(); //[4,3,2,1] 12 array === result; //false 13 array; //[1,2,3] 14 15 nativeArray.push(4); 16 var nativeResult = nativeArray.reverse(); //[4,3,2,1] 17 nativeResult === nativeResult; //true 18 nativeArray; //[4,3,2,1] ←不変配列 ←組み込みの配列 使い方と挙動に 違いはない 不変配列では元の配列は 変わっていない 組み込みの配列では 元の配列は変わっている
  19. 19. まとめ 関数型の機能をES6でどう実現するかを見てきた 1. 変更不可能な変数の宣言 2. パターンマッチ 3. 再帰による繰り返し処理 4. 不変なデータ構造 → const宣言子 → 分割代入 → 末尾呼び出し最適化 → プロキシ ECMAScript6の表現力で 関数型プログラミングを楽しもう!

×