Upload
ryuma-tsukano
View
5.357
Download
0
Embed Size (px)
DESCRIPTION
jsCafe16(2013/11/10)で話したjavascriptでの関数型プログラミング入門の話です。
Citation preview
関数型プログラミングin javascript
ryuma tsukano
2013/11/10 jsCafe16
目次
● 概要● 規律● 慣例● 適用
概要
関数型プログラミングとは?
数学上の関数を用いて
プログラミングする考え方の事
そして、言い換えると
規則と慣例ガチガチな理想郷の事
関数とは
ここで言う数学上の関数は
普段書いてるプログラムの関数と異なる
y = f(x)
上記のfの事。(写像)
入力値xに変換処理をして出力値yを求める
普段の関数と何が違うのか?
● 普段書いてる関数:○ 入力/出力値と関係無い変数を変える事もある○ レシーバに破壊的操作を伴う事もある○ 戻り値が無い事もある○ 引数以外の変数に依存する事もある
● 関数型の関数:○ 上に書いた事全部禁止。○ y=f(x)の様に入力を出力に変える事だけを行う
=>関数型には、厳しい規律と慣例がある
規律と慣例
● 規律○ 変数:変更不可○ 関数:参照透過かつ副作用が無い
● 慣例○ 第1級/高階関数○ 無名関数/lambda○ 再帰○ 部分適用/カリー化
※このまとめ方(規律と慣例という分け方)は独自で、人に話しても通じないので、ご注意を。但し、この中の各キーワードはどの書籍も大体一緒。
こんなに厳しくて何が嬉しい?
● 並列処理と相性が良いと言われる○ 値が入力値にのみ依存してる=状態共有不要○ map & reduceは高階関数を参考にしてる
● software固有の複雑性を単純化できる○ code levelで多くのメリットがある○ これについて、次のページで詳しく見る。
参考:なぜ関数プログラミングは重要か
code levelの幸せ
● 読み易い● test書き易い● debugし易い● Errorが起こり辛い● 階層が深くならない● 関数を再利用し易い
...と言われてる。
OOPよりもっと低Layerの話
同じ様な低layarのcoding作法といえば
関数型と関係なく有名な書籍沢山ありますね
● プログラミング作法● code complete● clean code / clean coder● リーダブルコード...etc...
それぞれ指摘はバラバラ。(時代も違うしね)これらと比べ関数型はゴールが明快。
関数型言語と仲間達。
● 関数型言語(純粋〜非純粋)○ Haskell/scala/OCaml/LISP/F# etc
■ 言語によりruleの強制度合いが違う
● 関数型「っぽい」言語○ ruby/javascript/C#
■ 言語Lvでsupport。例えばrubyはcurryがある
● 手続き型言語○ C/java/perl
■ とはいえ関数型的な機能も追加され境目は曖昧
なぜjsで関数型?
● jsは、他の言語と比べても特に○ 安全でない
■ 空値比較周りetc○ library競合
■ js/DOM/jQuery/Backbone/underscore etc...○ 癖強い
■ 生each..etc...
これらを
理解し易く整理=>test=>安全に管理する!
=>そのために「関数型」の考え方を取り入れる
jsには関数型programmingをやるbaseとして
underscoreがある。
● 基本的な関数揃えている● browser毎の動作の違い意識しなくて済む
○ ECMAScriptで使える関数増えているが、実際のUserのbrower状況を考えると、こういうlibの方が良い
● 有り難い事に、和訳有るみたい。
underscore.js !
関数型javascriptで本も出た。
O'Reillyから。
今年’13夏※この資料作成でも参考にさせて頂いた
※web link書籍以外でも、ここ数年、関数型+jsについて
blog記事や勉強会でもさかんに扱われてる
おすすめリソース
● 書籍○ Functional Javascript:jsでどう書くか例いっぱい○ scalaで学ぶ関数脳入門:関数型言語系で1番分り易い
○ 関数型言語titleの本以外にも、プログラミングの基礎系
の本でも幾つか関数型扱ってる物あり
● webサイト○ CAさんの記事:基礎から簡単な実例まで。説明丁寧○ jsと関数型のgist記事:良記事。Bad pattern記述有○ jsカリー化qiita記事:とても丁寧で分かり易い。
● slideshare○ 入門(link1,link2) : 面白いし、一通り分かる。○ js関数型 : ES6 Arrow funcやTS/自作monad等面白い
規律
規律編から
● ①変数の話● ②関数の話
※但し、実際は規律というより理想。
● 結局、完全に守る事は出来ない。● 出来るだけ守るべき理想の話。
規律①変数は変更できない
変数は変更できない like 定数
● A)値の変更不可能(immutable)● B)値の再代入が禁止
● Aについてjavascriptの変数は○ string/number = immutable○ object/arrray = mutable
■ 後者に注意● 関数内でも参照渡ししてたら変更不可能
● Bについてどの型でも再代入は禁止
変数は変更できない
例)元の変数の破壊的操作は禁止
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
× ○
あれ?
引数で参照渡しされたarrayは変更しないけど
local変数は変わってるんじゃない?
例外)関数内のlocal変数
関数がimmutableな戻り値を生み出すために、幾つかのlocal変数を変える事は出来る?
● from http://clojure.org/transients
=> Yeslocal変数は関数内で変更して良い
● その方が速度早い場合が多くあるため○ 教科書通りにいくと、再帰を使うべき
○ だが、実行速度や遠回りな記述を避けるため、例外的に
許可して良い(by oreilly本/scala本)○ 例えばunderscore内部も結構local変数を変えてる○ とはいえ可能な限りlocal変数も変化無しが望ましい
なぜ変数を変更しないのか?
programのバグ等の問題の主な原因は、
気軽に状態を変えてしまう事から来る。
● 例)function(a) { return 2a * PI ;} ○ 開発時)PI=3.14で面積計算test通過!○ リリース)PI=誰か別の値入れててerror
逆に状態を変えれない=>安全にcodingできる
実際のmutable
mutable(変数変更可能)が便利なのも事実。
● 例)backbone○ viewがthis.collectionにある関数をbind
■ Collection変更=>viewで関数を実行!○ =>コレできないとMVPも成り立たないし
必須でmutableにした方が良い所はそのまま
但し必要以上にmutableにしない方が良い。
=>理想像を描いている。
変数 in 関数型
ちなみに
関数型の名前の通りメインが関数になるので、
変数に何か値を入れる機会は減る
基本は、全部関数に任せる形になる。
=>次は、メインとなるこの関数の話
規律編②関数は参照透過で副作用無し
参照透過性(参照透明)※reference transparency● どこで関数callしても同じ引数で同じ値返す
○ そのため、どこでも関数を評価値に置換できる○ 実装した時に戻り値が確定すれば=>bug減る
● 同条件満たす = 参照透過性が高い○ 一見、当たり前のように聞こえるが...
● 例)右記の例だと○ 引数が1 =>同じ結果じゃない!○ 参照透過性は無い。参照不透明○ こうならないようにする事
var cnt = 1;function func(num) { return cnt + num;}func(1); // 2cnt = 100;func(1); // 101
×
副作用とは?
● 副作用=評価値算出以外の作業○ global変数いじる事○ fileや画面等に出力する事
■ document.writeとか...
● 副作用が無い○ 上記の余計な事をしない事
pureな関数
2つの条件
● 参照透過が高い(input:外部に依存してない)● 副作用が無い(output:外部に影響しない)
これらを満たす関数:pureな関数
満たさない関数:impureな関数
関数型は入出力を独立させてsimpleさを維持するために、pureな関数を作る事を勧めてる。
理想と現実
但し、pureな関数も理想
● 副作用(html出力等)無いとweb作れない● OOPのclassはどうしても参照透過性を破る
大事なのは、
● impureな関数と● pureな関数を
分離する事。
※少しでもpureな関数増やして綺麗にする事
眠くなってきた?
気持ち分かりますりん!
一緒に背伸びするりん!
沢山Keyword出てきて、ドン引きしました?
これから、更に沢山のkeyword出てきます。
※ここで19:50になってたら、今日は終了。高砂市(たかさご)のゆるキャラ
「ぼっくりん」
慣例
慣例編
関数型で多く見られる慣例的な表現が以下
1. 第1級/高階関数とchain2. 無名関数/lambda/closure3. 再帰4. 部分適用/カリー化
強制力は無いが、書くと「っぽく」なる○ ちなみに殆ど全部関連付いてる
慣例①第1級/高階関数とchain
例えば。
y = f(x)
このxやyいずれかが関数である時、
● 関数xやy=第1級関数 ※first class function○ 関数の引数/戻り値いずれかに指定された関数の事
● 関数f=高階関数 ※higher order function○ 関数の引数/戻り値いずれかに関数を指定した関数の事
代表的な高階関数(with underscore)
map:配列内の値にある操作をして返す
filter:配列内から条件を満たす物のみ返す
reduce:配列内の値を集計して返す
全部、underscoreで使える。 from http://underscorejs.org/
第一級関数/高階関数を使うメリット
細かく処理を分けれる => 幾つかメリット
● 例)○ ループ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 + 再帰で綺麗に整理できる(後述)
関数のchain
第1級の関数=>関数をchainできる(pipeline)● ※厳密にはchainingとpipelineは違うが
image)関数=>関数=>関数=>...
例)_.compose(_circulate(length), _stepIndex)(index, ((i) -> i+1))
この慣例に沿う事で変数変更最小限にできる
慣例②無名関数/lambda
無名関数(匿名関数)● 名前の無い関数
○ ある関数の利用が限定的な時に使う○ 変数に入れるとlambda関数と呼ぶ
■ var x = function() { … }○ jsで関数リテラル + 即時関数で無名関数よく見る
関数型では関数を頻繁に書く事になるので、
単発的な関数は無名にするのが望ましい。
※名前空間を意味なく汚さないため。
クロージャ
クロージャ:closure● 無名関数でLexical Scopeに束縛された自由変
数(関数内の引数やlocal変数)を持つ○ Lexical Scope = 構文で決定できるスコープ○ 自由変数=引数の時に部分適用の話に続く(後述)○ jsにprivateが無いので、その代わり
function human(num) { var age = 18; return function() { return age; }}human()() // 18
慣例③再帰関数
ある関数の中で自分自身の関数を呼び出す
like マトリョーシカ...
function add(n) { if(n == 0){ return 0 } else { return n + add(n-1); }}(10) // => 55
なぜ再帰を使う必要がある?
例)再帰部を使ってない例
変数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));
なぜ再帰を使う必要がある?
ループの代わりに再帰
● 変更してしまう先程のlocal変数無くせた
function summRec(array, seed) { if(_.isEmpty(array)) { return seed; } else { return summRec(_.rest(array), _.first(array) + seed); // 末尾再帰(後述) }}summRec(_.range(1,11), 0);
再帰を書く時のコツ
昔からのコツがあるらしい(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));}
スタック
関数実行時、スタックに以下が積まれる
● local変数● 呼び出し元アドレス● 関数の引数等
=>関数終了時に解放される
=>再帰は解放されない
=>stack over flowになる。RangeError: Maximum call stack size exceeded
末尾再帰最適化
一部の他言語で末尾再帰にすると
最適化されて関数内の毎回のstackを破棄可能
=>stack over flowを回避する。
● ※末尾再帰=最後に再帰で引数だけで解決○ (前例のsumRecがそう。)
javascriptは?
=>対応してないらしい!
=回数やsizeに注意
慣例④部分適用とカリー化
● 部分適用○ 複数の引数を取る関数に、一部の引数のみ値を束縛し
た新しい関数を返す操作○ 共通の引数を使い回したい時に使う
underscoreの_.partial(_.bindも出来る)● ※_.partialはver1.4.4からなので注意
カリー化 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”)
適用
現実的に関数型書くの?
色んなトピックがある
● OOP vs FP● どこで書けば良いの?● 自分の書いたソースは関数型なの?
OOP vs FP
関数型はOOPと矛盾する所/共通する所がある
dataも一々zipするか否か等議論あるが、
そんなに関数型頑張らなくて良いかと。
変数freeze ? そこまでやる?
基本的に、いつも通りのOOPで、
複数のArray/Objectを操作している所で
関数型思い出せばよいのでは?
どこで書くのか?
シチュエーション
● 複数Model集計/操作/取得処理○ ex:Backbone.Collection周り
● library系の関数○ ex:決まったjsonのparse処理とか
● Refactoring時に意識するのも効果的○ =>次のページへ
refactoringと関数型
DOM/jQuery等でsource汚した時、
1. 規律の視点で批判的に見直す2. 規律で直して慣例表現書けたら書く
程度のcasualな採用で良いのでは。
=>DOM event/汎用的な関数分ける=>test整理=>切り出した汎用的な関数を再利用=>Happy!● 良い記事
○ refactoringの記事○ BadPatternまとめ
まとめ
● 規律を守る(理想郷を目指す)○ 変数は変えない○ 関数は入出力を外部と独立
● 慣例を幾つか知っておく○ 高階関数で第一級関数を繋ぐ○ 無名関数ラムダ関数使う○ 再帰でloop counterや結果の変数のmutableを回避○ 部分適用で引数入れた関数を使い回す
● 関数型で無理しない○ OOPで解決できる問題はOOPでいいんじゃない○ 複数Model周辺の処理やrefactoring時に思い出す
おしまい