Javascriptで関数型プログラミングっぽいことをしてみる

Javascriptで関数型プログラミングっぽいことをしてみる:


やること

Javascriptで関数型プログラミングを少し触ってみる。

で、Reactを勉強するための下地を作る。

りあクト!を読んだ勢いです。


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

何をもって関数型プログラミングとするか、ということに関して、関数型プログラミングのコミュニティ内でも正確な定義や合意というものは存在しない。したがって関数型言語の定義も明確な境界はない。ただし、手続き型プログラミングが命令実行の列としてプログラムを記述していくのに対し、関数型プログラミングは複数の式を関数の適用によって組み合わせていくプログラミングスタイルである。


たとえば、1 から 10 までの整数を足し合わせるプログラムを考えるとき、手続き型プログラミングでは以下のようにループ文を使って一時変数に数値を足していく(一時変数の内容を書き換える)命令を繰り返し実行するという形を取る。


Pascalによる例:
program test; 
var total, i : Integer; 
begin 
total := 0; 
for i := 1 to 10 do 
    total := total + i; 
WriteLn(total) 
end. 
F#による例:
printfn "%d" (let rec sum x = if x > 0 then x + sum (x - 1) else 0 sum 10) 
Wikipediaから抜粋ですが、直感的にわかりやすいと思います。

個人的には「副作用という名の複雑さをなくそうよって考えの元に設計してコーディングするのが関数型プログラミング」かなーなんて思ってます。え?それって手続き型じゃないかって?上の例を見て下さい。コスモを感じるのです(適当)


何故Javascriptで関数型プログラミング?

Reactを触っているからです...React触って無かったら興味あるある詐欺してますね、間違いないです。

React自体何で関数型なのかと言うのは色々とあると思いますが、DOMのインタラクティブな操作を行う時に最適だと思ったとかそういうことなんじゃないですかね。難しいことは追々理解していこうと思います。

Javascriptは基本的に非同期で処理が行われていくのでオブジェクトペースだと思わぬ副作用(想定と異なる振る舞い)が起こるので関数型プログラミングの方が扱いやすいというのはわかる気がします。

オブジェクトが状態と処理を同時に持つとするなら関数は状態は保持せず処理のみを持つものってことっすね(状態を持つ必要がある場合は状態を受け取って状態を返す等、処理結果が一定であることが担保されていることが大事とかそういうお話)。

難しいことは置いといて早速触っていきます。

ちなみにOOPから関数型プログラミングを勉強するならScalaが一番良いと思います。そうじゃなくて純粋な関数型プログラミングを勉強するならHaskellでしょうか。純粋な関数型プログラミングを勉強する場合は数学の勉強も必要になると思います。

自分はReactありきで関数型プログラミングを勉強するのでJavascriptですが...。


コレクションの反復処理(Iteration)

先に例に上げたような形と似ていますがコレクション(配列)に対する処理を関数型でやっつけてみます。

const ary = [1, 2, 3, 4, 5]; 
console.log(ary.map(i => i + 1)); 
例ではmapしか出しませんが、filter, find, reduce等々あります。

これらは元の配列を操作せず新しい配列を生成して返しています。こういう副作用を生まない処理は関数型プログラミングと相性が良いです(今の所そんな気がしてます)。

Javascriptでの関数型プログラミングでは、

  1. 無名関数はそこらじゅうで使われる
  2. 変数に関数をぶち込める
  3. 関数の引数/戻り値に関数を指定できる
  4. 関数から関数を作れる
のようなことが散見されます。

1と2はJavascriptをかじってるとわかるかと思います。3と4はこれから見ていこうと思います(3もJavascriptをかじってるとわかると思いますが、一応)。


高階関数(Higher order function)

何か偉そうな名前です。名前の通り割と偉いことできます。

先に書きましたが、関数を引数/戻り値に指定できる関数を高階関数と言います。

とは言え先に書いたmapが既に関数を引数に取ってますよね。そういうことです。

と言うことで高階関数を書いてみましょう。

const hoge = (i, fnc) => { 
  return f => fnc(f + i); 
} 
const piyo = hoge(5, n => n * 2); 
console.log(piyo(1)); // 12 
こんな感じですね。ちょっと説明を入れていきます。まずはpiyoについてです。

piyoは先の変数に関数をぶち込めるってやつですね。つまり、

function piyo(n) { 
  return n * 2; 
} 
とほぼほぼ同義です。で、これをhogeが引数として受け取っています。hogeのreturnを見ていくとこれも関数になっていますね。つまり、

// iはhogeの第一引数(5) 
function returnFnc(f) { 
  retutn fnc(f + i); 
} 
となってる感じです。fnc(1 + 5)してる感じです。

fnc(f + i) 
この部分です。このfncはhogeの第二引数の関数になります。中身はシンプルに引数を2倍しているだけですね。

整理するとhoge関数は、「引数を1つ受け取れて、受け取った引数にhoge関数の第一引数を加算して、2倍にした値を返す関数、を返す関数」となります。はい、意味不明ー何言ってんだお前ーってなると思いますが、とりあえずこういうことできるんだなぐらいに覚えておけばOKだと思います。

ちなみにhogeは、

const hoge = (i, fnc) => f => fnc(f + i); 
こう書くこともできます(returnの省略)。ここまで来ると読み慣れてないと「???」ってなります。僕もなります。


関数から関数を作る

関数から関数を作る前にカリー化の話をしましょう。

僕は辛いのがあまり得意じゃないんですけどJavascriptにおけるカリー化はそこまで辛くありません。不幸中の幸いですね(?)

※Javascript以外で関数型プログラミングしたことが無いので「Javascriptにおけるカリー化」と書きました。


カリー化

かんたーーーんに言うと「複数の引数がある関数を分割してネストする」ことをカリー化って言います。何言ってんだお前、わかる。

百聞は一見に如かず。

// カリー化してない 
const sum1 = (i, j) => i + j; 
console.log(sum1(1, 4)); 
 
// カリー化してる 
const sum2 = i => j => i + j; 
console.log(sum2(1)(4)); 
さっきの高階関数が何となくわかるとスッと入ってくると思います。

で、カリー化って何の役に立つの?シンプルにするだけ?シンプルじゃなくても副作用とか起きないんじゃねーの?おぉん?となりそうです。カリー化の真骨頂は関数をシンプルに保つという面もあるんですが、関数から関数を作るところにあるんじゃないかなって思います。それが関数をシンプルに保つってことになるんですが。無限ループ。


関数から関数を作る

先程書いた加算するだけの関数を書きましたね。その派生ではありますが、

1. 常時1を加算する関数

2. 常時2を加算する関数

3. 常時3を加算する関数

4. 常時4を加算する関数

5. 常時5を加算する関数

を作ってみましょう。

const sum1 = i => i + 1; 
const sum2 = i => i + 2; 
const sum3 = i => i + 3; 
const sum4 = i => i + 4; 
const sum5 = i => i + 5; 
 
console.log(sum1(1)); 
って感じっすね。いやーーーーーーCoolじゃない。このCoolじゃない関数を砂漠に投げ捨ててCoolに関数を作れるのがカリー化の為せる技です。では、早速。

const sum = i => j => i + j; 
const sum1 = sum(1); 
const sum2 = sum(2); 
const sum3 = sum(3); 
const sum4 = sum(4); 
const sum5 = sum(5); 
 
console.log(sum1(1)); 
Coolじゃない方は128byte, Coolな方は138byte。byte数勝負じゃ負けましたが圧倒的Cool差です。


何やってるかと言うとカリー化した関数の一部の引数を固定して新しい関数を作っているだけですね。今回で言えばネストしている関数のアウター側の引数を固定化した感じになります。

const sum1 = 1 => { 
  return j => 1 + j; 
} 
イメージとしてはこんな感じ。


最後に

これでReactを勉強する最低限の下地はできた感あります。componentを引数にとってcomponent返すとかReactだとあるらしいので高階関数に関してはほぼほぼ必須感あります。カリー化はどこでどう使うのかわかんないっすけど抑えといた方が良いってことなんで一応。


それとnpmモジュールの引数が高階関数だったりすることもあるので、Node.jsやろうかなーって思ってる方もこの辺は必須になるのかも。

で、関数型プログラミングって何よ?って話ですが、多分これが関数型プログラミングだーってのは無さそうな感じがします。あくまでも副作用という名の複雑さをなくそうよって考えの元に設計してコーディングするのが関数型プログラミングなのかなと思います。逆に言えばこれらができれば無名関数も高階関数もカリー化も無くなって構わないんだと思います。その思想を保つために便利なだけであって必須ではない、という感じでしょうか。

副作用が悪かと問われると必ずしもそうじゃないし、むしろ副作用を期待してOOPしてる時もあります。ただそれが純粋なものであるかどうかで言えば疑わしいところはあると思います。

引数・戻り値無し、引数無し、戻り値無し、そんな関数を書き始めたら注意しようNE☆


おまけ

関数型プログラミングでコードを書いていて思ったことがいくつかあるのですが、その最たるものが「関数の結果がコンテキストに依存しないためテストしやすい」です。後は関数が相互影響しないので並列処理とか書きやすそうだなって思いました。この辺はJavascriptと親和性が高そうです。


逆に状態に依存した処理を書くのは難しそう(その辺はFrameworkに乗れば良いんだろうけど)。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)