+ All Categories
Home > Engineering > ECMAScript6による関数型プログラミング

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

Date post: 02-Jul-2015
Category:
Upload: tanukkii
View: 4,483 times
Download: 5 times
Share this document with a friend
Description:
2014/10/29のES6+カジュアルトークで発表した資料です。
19
ECMAScript6による 関数型プログラミング 株式会社トライフォート 安田裕介
Transcript
Page 1: ECMAScript6による関数型プログラミング

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

株式会社トライフォート 安田裕介

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

自己紹介• 名前:安田裕介

• Trifortに今年入社の新卒1年生

• Webフロントエンジニアやってます

• JavaScript, Scala, C++が好き

• GitHubアカウント: TanUkkii007

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

関数型プログラミングとは?

• 第一級オブジェクトとしての関数

• イミュータビリティ(不変性)

副作用を排除し関数オブジェクトを駆使する プログラミングパラダイム

関数型プログラミングの2大構成要素

拡張性と保守性の高いコードを書く手法として 近年注目を集めている

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

JavaScriptと 関数型プログラミングの関係JavaScriptは関数型プログラミング言語である

Schemeの第一級関数オブジェクトを受け継いだ言語

ECMAScript6は関数型の以下の機能をも可能にする

1. 変更不可能な変数の宣言 2. パターンマッチ 3. 再帰による繰り返し処理 4. 不変なデータ構造

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

1.変更不可能な変数の宣言

• 関数型プログラミングでは値の変更を行わない

• 変数への再代入を行わない

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

const宣言子

※strictモードの場合、再代入しようとするとTypeErrorとなる

※strictモードでない場合、再代入は暗黙に失敗する

※letと同様ブロックスコープをもつ

再代入できない変数を宣言する

1 "use strict"; 2 const foo = "foo"; 3 4 foo = “bar”; 5 //TypeError: foo is read-only

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

2.パターンマッチ

• 関数型プログラミングではパターンマッチによる値の取り出しを行う

• パターンマッチにより代入などの副作用を減らすことができる

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

分割代入(デストラクチャリング)

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ループや関数の引数でも使えます

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

3.再帰による繰り返し処理

• 繰り返し処理の方法として再帰とループの2つの方法がある

• 再帰の方が代入などの副作用がなく、短く書ける

• 関数型プログラミングでは繰り返し処理に再帰を使う

• 末尾呼び出し最適化により再帰でのスタックオーバーフローを回避する

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

末尾呼び出し最適化関数呼び出しが末尾呼び出しかどうかを判定し、

末尾呼び出しの場合、最適化する

call(call(call(call())))

• 関数は呼び出し元に戻るため、その位置を記憶する

• でもそれが関数本体の末尾なら、戻る必要はない

そこで末尾呼び出し最適化がおきる

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

例)階乗の計算 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

←再帰ではスタックオーバーフロー

←ループではInfinityではあるが成功

←これが末尾呼び出し

末尾呼び出し最適化が実装されれば 解決される!!

@k_matsuzaki さんに指摘してもらいました。これでは最適化されません。正しくは↓

function factorial1(n, acc) { if (n == 0) return acc; return factorial1(n - 1, n * acc); }

factorial1(100000, 1);

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

4.不変なデータ構造

• 関数型プログラミングにおける配列やリストなどのデータ構造は不変であり、自身の値を変更しない

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

プロキシ既存のオブジェクトをラップし、その一部の内部メソッドをECMAScriptコードで実装して挙動を

変えることを可能にするオブジェクト

プロキシオブジェクトの作り方!!!new Proxy(target, { get: //proxy[name] set: //proxy[name] = val apply: //proxy() construct: //new Proxy() deleteProperty: //delete proxy[name] })

ラップするターゲットオブジェクト内部メソッドに実装を与えるハンドラーオブジェクト

がおきたときの処理を 定義できる

※ハンドラーの各メソッドをトラップという ※トラップは全部で14個ある  ※定義されていないトラップ

ではデフォルトの挙動が用いられる ※apply, constructトラップは関数がターゲットとなるときのみ有効

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

例)プロキシによる イミュータブルな配列

pop, push, reverse, shift, sort, splice, unshift

関数型プログラミングでは値を変更しない ES6のプロキシを使って不変な配列を作ってみよう

方針JavaScriptの配列には自身を変更する破壊メソッドがある

破壊メソッドにsliceを挟むことで非破壊メソッドに変える

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

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 };

破壊メソッドの判定用の配列

通常の配列と同様 Arrayコンストラクタで 配列を作れるようにする

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

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 };

プロキシを 作成しているのはここ

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

!!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 }

ターゲットに配列を使用

コピーした配列で プロキシを再作成

破壊メソッドなら sliceを挟む関数を返す

getトラップ を定義

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

プロキシで作った不変配列は配列とどう違うのか?

!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]

使い方と挙動に 違いはない

不変配列では元の配列は 変わっていない

組み込みの配列では 元の配列は変わっている

←不変配列←組み込みの配列

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

まとめ

ECMAScript6の表現力で 関数型プログラミングを楽しもう!

1. 変更不可能な変数の宣言 2. パターンマッチ 3. 再帰による繰り返し処理 4. 不変なデータ構造

→ const宣言子 → 分割代入 → 末尾呼び出し最適化 → プロキシ

関数型の機能をES6でどう実現するかを見てきた


Recommended