関数型プログラミング in javascript

52
関数型プログラミング in javascript ryuma tsukano 2013/11/10 jsCafe16

description

jsCafe16(2013/11/10)で話したjavascriptでの関数型プログラミング入門の話です。

Transcript of 関数型プログラミング in javascript

Page 1: 関数型プログラミング in javascript

関数型プログラミングin javascript

ryuma tsukano

2013/11/10 jsCafe16

Page 2: 関数型プログラミング in javascript

目次

● 概要● 規律● 慣例● 適用

Page 3: 関数型プログラミング in javascript

概要

Page 4: 関数型プログラミング in javascript

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

数学上の関数を用いて

プログラミングする考え方の事

そして、言い換えると

規則と慣例ガチガチな理想郷の事

Page 5: 関数型プログラミング in javascript

関数とは

ここで言う数学上の関数は

普段書いてるプログラムの関数と異なる

y = f(x)

上記のfの事。(写像)

入力値xに変換処理をして出力値yを求める

Page 6: 関数型プログラミング in javascript

普段の関数と何が違うのか?

● 普段書いてる関数:○ 入力/出力値と関係無い変数を変える事もある○ レシーバに破壊的操作を伴う事もある○ 戻り値が無い事もある○ 引数以外の変数に依存する事もある

● 関数型の関数:○ 上に書いた事全部禁止。○ y=f(x)の様に入力を出力に変える事だけを行う

=>関数型には、厳しい規律と慣例がある

Page 7: 関数型プログラミング in javascript

規律と慣例

● 規律○ 変数:変更不可○ 関数:参照透過かつ副作用が無い

● 慣例○ 第1級/高階関数○ 無名関数/lambda○ 再帰○ 部分適用/カリー化

※このまとめ方(規律と慣例という分け方)は独自で、人に話しても通じないので、ご注意を。但し、この中の各キーワードはどの書籍も大体一緒。

Page 8: 関数型プログラミング in javascript

こんなに厳しくて何が嬉しい?

● 並列処理と相性が良いと言われる○ 値が入力値にのみ依存してる=状態共有不要○ map & reduceは高階関数を参考にしてる

● software固有の複雑性を単純化できる○ code levelで多くのメリットがある○ これについて、次のページで詳しく見る。

参考:なぜ関数プログラミングは重要か

Page 9: 関数型プログラミング in javascript

code levelの幸せ

● 読み易い● test書き易い● debugし易い● Errorが起こり辛い● 階層が深くならない● 関数を再利用し易い

...と言われてる。

OOPよりもっと低Layerの話

Page 10: 関数型プログラミング in javascript

同じ様な低layarのcoding作法といえば

関数型と関係なく有名な書籍沢山ありますね

● プログラミング作法● code complete● clean code / clean coder● リーダブルコード...etc...

それぞれ指摘はバラバラ。(時代も違うしね)これらと比べ関数型はゴールが明快。

Page 11: 関数型プログラミング in javascript

関数型言語と仲間達。

● 関数型言語(純粋〜非純粋)○ Haskell/scala/OCaml/LISP/F# etc

■ 言語によりruleの強制度合いが違う

● 関数型「っぽい」言語○ ruby/javascript/C#

■ 言語Lvでsupport。例えばrubyはcurryがある

● 手続き型言語○ C/java/perl

■ とはいえ関数型的な機能も追加され境目は曖昧

Page 12: 関数型プログラミング in javascript

なぜjsで関数型?

● jsは、他の言語と比べても特に○ 安全でない

■ 空値比較周りetc○ library競合

■ js/DOM/jQuery/Backbone/underscore etc...○ 癖強い

■ 生each..etc...

これらを

理解し易く整理=>test=>安全に管理する!

=>そのために「関数型」の考え方を取り入れる

Page 13: 関数型プログラミング in javascript

jsには関数型programmingをやるbaseとして

underscoreがある。

● 基本的な関数揃えている● browser毎の動作の違い意識しなくて済む

○ ECMAScriptで使える関数増えているが、実際のUserのbrower状況を考えると、こういうlibの方が良い

● 有り難い事に、和訳有るみたい。

underscore.js !

Page 14: 関数型プログラミング in javascript

関数型javascriptで本も出た。

O'Reillyから。

今年’13夏※この資料作成でも参考にさせて頂いた

※web link書籍以外でも、ここ数年、関数型+jsについて

blog記事や勉強会でもさかんに扱われてる

Page 15: 関数型プログラミング in javascript

おすすめリソース

● 書籍○ Functional Javascript:jsでどう書くか例いっぱい○ scalaで学ぶ関数脳入門:関数型言語系で1番分り易い

○ 関数型言語titleの本以外にも、プログラミングの基礎系

の本でも幾つか関数型扱ってる物あり

● webサイト○ CAさんの記事:基礎から簡単な実例まで。説明丁寧○ jsと関数型のgist記事:良記事。Bad pattern記述有○ jsカリー化qiita記事:とても丁寧で分かり易い。

● slideshare○ 入門(link1,link2) : 面白いし、一通り分かる。○ js関数型 : ES6 Arrow funcやTS/自作monad等面白い

Page 16: 関数型プログラミング in javascript

規律

Page 17: 関数型プログラミング in javascript

規律編から

● ①変数の話● ②関数の話

※但し、実際は規律というより理想。

● 結局、完全に守る事は出来ない。● 出来るだけ守るべき理想の話。

Page 18: 関数型プログラミング in javascript

規律①変数は変更できない

変数は変更できない like 定数

● A)値の変更不可能(immutable)● B)値の再代入が禁止

● Aについてjavascriptの変数は○ string/number = immutable○ object/arrray = mutable

■ 後者に注意● 関数内でも参照渡ししてたら変更不可能

● Bについてどの型でも再代入は禁止

Page 19: 関数型プログラミング in javascript

変数は変更できない

例)元の変数の破壊的操作は禁止

function push1 (array) { array.push(1); return array};var a = [1,2,3];var b = push1(a);console.log(b); // [1,2,3,1]console.log(a); // [1,2,3,1]

function push1 (array) { var copied = array.slice(); copied.push(1) return copied;};var a = [1,2,3];var b = push1(a);console.log(b); // [1,2,3,1]console.log(a); // [1,2,3]

参考:https://gist.github.com/ympbyc/5564146

× ○

Page 20: 関数型プログラミング in javascript

あれ?

引数で参照渡しされたarrayは変更しないけど

local変数は変わってるんじゃない?

Page 21: 関数型プログラミング in javascript

例外)関数内のlocal変数

関数がimmutableな戻り値を生み出すために、幾つかのlocal変数を変える事は出来る?

● from http://clojure.org/transients

=> Yeslocal変数は関数内で変更して良い

● その方が速度早い場合が多くあるため○ 教科書通りにいくと、再帰を使うべき

○ だが、実行速度や遠回りな記述を避けるため、例外的に

許可して良い(by oreilly本/scala本)○ 例えばunderscore内部も結構local変数を変えてる○ とはいえ可能な限りlocal変数も変化無しが望ましい

Page 22: 関数型プログラミング in javascript

なぜ変数を変更しないのか?

programのバグ等の問題の主な原因は、

気軽に状態を変えてしまう事から来る。

● 例)function(a) { return 2a * PI ;} ○ 開発時)PI=3.14で面積計算test通過!○ リリース)PI=誰か別の値入れててerror

逆に状態を変えれない=>安全にcodingできる

Page 23: 関数型プログラミング in javascript

実際のmutable

mutable(変数変更可能)が便利なのも事実。

● 例)backbone○ viewがthis.collectionにある関数をbind

■ Collection変更=>viewで関数を実行!○ =>コレできないとMVPも成り立たないし

必須でmutableにした方が良い所はそのまま

但し必要以上にmutableにしない方が良い。

=>理想像を描いている。

Page 24: 関数型プログラミング in javascript

変数 in 関数型

ちなみに

関数型の名前の通りメインが関数になるので、

変数に何か値を入れる機会は減る

基本は、全部関数に任せる形になる。

=>次は、メインとなるこの関数の話

Page 25: 関数型プログラミング in javascript

規律編②関数は参照透過で副作用無し

参照透過性(参照透明)※reference transparency● どこで関数callしても同じ引数で同じ値返す

○ そのため、どこでも関数を評価値に置換できる○ 実装した時に戻り値が確定すれば=>bug減る

● 同条件満たす = 参照透過性が高い○ 一見、当たり前のように聞こえるが...

● 例)右記の例だと○ 引数が1 =>同じ結果じゃない!○ 参照透過性は無い。参照不透明○ こうならないようにする事

var cnt = 1;function func(num) { return cnt + num;}func(1); // 2cnt = 100;func(1); // 101

×

Page 26: 関数型プログラミング in javascript

副作用とは?

● 副作用=評価値算出以外の作業○ global変数いじる事○ fileや画面等に出力する事

■ document.writeとか...

● 副作用が無い○ 上記の余計な事をしない事

Page 27: 関数型プログラミング in javascript

pureな関数

2つの条件

● 参照透過が高い(input:外部に依存してない)● 副作用が無い(output:外部に影響しない)

これらを満たす関数:pureな関数

満たさない関数:impureな関数

関数型は入出力を独立させてsimpleさを維持するために、pureな関数を作る事を勧めてる。

Page 28: 関数型プログラミング in javascript

理想と現実

但し、pureな関数も理想

● 副作用(html出力等)無いとweb作れない● OOPのclassはどうしても参照透過性を破る

大事なのは、

● impureな関数と● pureな関数を

分離する事。

※少しでもpureな関数増やして綺麗にする事

Page 29: 関数型プログラミング in javascript

眠くなってきた?

気持ち分かりますりん!

一緒に背伸びするりん!

沢山Keyword出てきて、ドン引きしました?

これから、更に沢山のkeyword出てきます。

※ここで19:50になってたら、今日は終了。高砂市(たかさご)のゆるキャラ

「ぼっくりん」

Page 30: 関数型プログラミング in javascript

慣例

Page 31: 関数型プログラミング in javascript

慣例編

関数型で多く見られる慣例的な表現が以下

1. 第1級/高階関数とchain2. 無名関数/lambda/closure3. 再帰4. 部分適用/カリー化

強制力は無いが、書くと「っぽく」なる○ ちなみに殆ど全部関連付いてる

Page 32: 関数型プログラミング in javascript

慣例①第1級/高階関数とchain

例えば。

y = f(x)

このxやyいずれかが関数である時、

● 関数xやy=第1級関数 ※first class function○ 関数の引数/戻り値いずれかに指定された関数の事

● 関数f=高階関数 ※higher order function○ 関数の引数/戻り値いずれかに関数を指定した関数の事

Page 33: 関数型プログラミング in javascript

代表的な高階関数(with underscore)

map:配列内の値にある操作をして返す

filter:配列内から条件を満たす物のみ返す

reduce:配列内の値を集計して返す

全部、underscoreで使える。 from http://underscorejs.org/

Page 34: 関数型プログラミング in javascript

第一級関数/高階関数を使うメリット

細かく処理を分けれる => 幾つかメリット

● 例)○ ループlogicだけ共通化 + 条件だけtest可能に

function filter(array, condition) { result = [] for (var i = 0; i < array.length; i++) { if (condition(array[i])){ result.push(array[i]); } }}checkEven = function(x){ return x % 2 == 0 }; // 本来Loop内にあった条件判定checkOdd = function(x) { return x % 2 == 1}; // 処理が、表に出てtestし易いfilter([1,2,3,4,5], checkEven);filter([1,2,3,4,5], checkOdd);

※このあたり、underscore + 再帰で綺麗に整理できる(後述)

Page 35: 関数型プログラミング in javascript

関数のchain

第1級の関数=>関数をchainできる(pipeline)● ※厳密にはchainingとpipelineは違うが

image)関数=>関数=>関数=>...

例)_.compose(_circulate(length), _stepIndex)(index, ((i) -> i+1))

この慣例に沿う事で変数変更最小限にできる

Page 36: 関数型プログラミング in javascript

慣例②無名関数/lambda

無名関数(匿名関数)● 名前の無い関数

○ ある関数の利用が限定的な時に使う○ 変数に入れるとlambda関数と呼ぶ

■ var x = function() { … }○ jsで関数リテラル + 即時関数で無名関数よく見る

関数型では関数を頻繁に書く事になるので、

単発的な関数は無名にするのが望ましい。

※名前空間を意味なく汚さないため。

Page 37: 関数型プログラミング in javascript

クロージャ

クロージャ:closure● 無名関数でLexical Scopeに束縛された自由変

数(関数内の引数やlocal変数)を持つ○ Lexical Scope = 構文で決定できるスコープ○ 自由変数=引数の時に部分適用の話に続く(後述)○ jsにprivateが無いので、その代わり

function human(num) { var age = 18; return function() { return age; }}human()() // 18

Page 38: 関数型プログラミング in javascript

慣例③再帰関数

ある関数の中で自分自身の関数を呼び出す

like マトリョーシカ...

function add(n) { if(n == 0){ return 0 } else { return n + add(n-1); }}(10) // => 55

Page 39: 関数型プログラミング in javascript

なぜ再帰を使う必要がある?

例)再帰部を使ってない例

変数result/iを変更してる=>望ましくない

※前述のlocal変数の例外該当するが。

function summ(array) { var result = 0; var sz = array.length; for(var i = 0; i < sz; i++) result += array[i]; return result;}summ(_.range(1,11));

Page 40: 関数型プログラミング in javascript

なぜ再帰を使う必要がある?

ループの代わりに再帰

● 変更してしまう先程のlocal変数無くせた

function summRec(array, seed) { if(_.isEmpty(array)) { return seed; } else { return summRec(_.rest(array), _.first(array) + seed); // 末尾再帰(後述) }}summRec(_.range(1,11), 0);

Page 41: 関数型プログラミング in javascript

再帰を書く時のコツ

昔からのコツがあるらしい(1990Touretzky)1. いつ停止するのか知る事2. どうstepを取るか決める

○ 再帰部で、問題が分解され小さくなるように

3. step内の小さい問題点を解決する事○ 基底部で、最小の問題を解決するように

例)右記の関数は

1. _.isEmpty2. 1+...3. _.rest()

function myLength(ary) { if (_.isEmpty(ary)) return 0; else return 1 + myLength(_.rest(ary));}

Page 42: 関数型プログラミング in javascript

スタック

関数実行時、スタックに以下が積まれる

● local変数● 呼び出し元アドレス● 関数の引数等

=>関数終了時に解放される

=>再帰は解放されない

=>stack over flowになる。RangeError: Maximum call stack size exceeded

Page 43: 関数型プログラミング in javascript

末尾再帰最適化

一部の他言語で末尾再帰にすると

最適化されて関数内の毎回のstackを破棄可能

=>stack over flowを回避する。

● ※末尾再帰=最後に再帰で引数だけで解決○ (前例のsumRecがそう。)

javascriptは?

=>対応してないらしい!

=回数やsizeに注意

Page 44: 関数型プログラミング in javascript

慣例④部分適用とカリー化

● 部分適用○ 複数の引数を取る関数に、一部の引数のみ値を束縛し

た新しい関数を返す操作○ 共通の引数を使い回したい時に使う

underscoreの_.partial(_.bindも出来る)● ※_.partialはver1.4.4からなので注意

Page 45: 関数型プログラミング in javascript

カリー化 currying

複数の引数をとる関数を、以下の関数にする事

● 引数:元の関数の最初の引数● 戻り値:元の残りの引数から結果を返す関数

あえてカリー化書く=>部分適用を明示

※curry切出しmax3階層位迄=>それ以上混乱。

function getUserInfo(lang) { return function(params) { return accessAPI(lang, params);}}jpUserInfo = getUserInfo(“japanese”)jpUserInfo(name: “taro”)jpUserInfo(name: “hanako”)

function getUserInfo(lang, params) { return accessAPI(lang, params);}}

getUserInfo(“japanese”, “taro”)getUserInfo(“japanese”, “hanako”)

Page 46: 関数型プログラミング in javascript

適用

Page 47: 関数型プログラミング in javascript

現実的に関数型書くの?

色んなトピックがある

● OOP vs FP● どこで書けば良いの?● 自分の書いたソースは関数型なの?

Page 48: 関数型プログラミング in javascript

OOP vs FP

関数型はOOPと矛盾する所/共通する所がある

dataも一々zipするか否か等議論あるが、

そんなに関数型頑張らなくて良いかと。

変数freeze ? そこまでやる?

基本的に、いつも通りのOOPで、

複数のArray/Objectを操作している所で

関数型思い出せばよいのでは?

Page 49: 関数型プログラミング in javascript

どこで書くのか?

シチュエーション

● 複数Model集計/操作/取得処理○ ex:Backbone.Collection周り

● library系の関数○ ex:決まったjsonのparse処理とか

● Refactoring時に意識するのも効果的○ =>次のページへ

Page 50: 関数型プログラミング in javascript

refactoringと関数型

DOM/jQuery等でsource汚した時、

1. 規律の視点で批判的に見直す2. 規律で直して慣例表現書けたら書く

程度のcasualな採用で良いのでは。

=>DOM event/汎用的な関数分ける=>test整理=>切り出した汎用的な関数を再利用=>Happy!● 良い記事

○ refactoringの記事○ BadPatternまとめ

Page 51: 関数型プログラミング in javascript

まとめ

● 規律を守る(理想郷を目指す)○ 変数は変えない○ 関数は入出力を外部と独立

● 慣例を幾つか知っておく○ 高階関数で第一級関数を繋ぐ○ 無名関数ラムダ関数使う○ 再帰でloop counterや結果の変数のmutableを回避○ 部分適用で引数入れた関数を使い回す

● 関数型で無理しない○ OOPで解決できる問題はOOPでいいんじゃない○ 複数Model周辺の処理やrefactoring時に思い出す

Page 52: 関数型プログラミング in javascript

おしまい