JavaScriptで実践!なんちゃって関数型プログラミングで九九表を作ってみよう!
こんにちは、プロダクト開発チームの明石です。
入社してはや、5ヶ月が経とうとしています。
いままで味わったことのなかった経験をいろいろしているのですが、一番強烈かつ困ったのが「関数型プログラミング」でした。
今回は、完全な関数型プログラミング初心者が「とりあえずできるように」なるまでに大事だと思った考えを書こうと思います。
弊社グラッドキューブでは、ほぼすべてのプロダクトで、「LiveScript」を使った、関数型プログラミングを採用しています。
(「LiveScript」というのはJavaScriptを簡単に書くことのできる、altJSの一種です。 「CoffeeScript」とか「TypeScript」とかが有名ですね。)
さて、関数型プログラミングは初めてでしたが、とりあえず、何事も経験だと意気込んで初めてみました。しかし、初心者のご多分に漏れず、いろんな入門ページを見ては
「参照透過性?」
「高階関数?」
「カリー化?」
「…わからない」
「…もうだめだ…」← New!
という展開をくりひろげていました。
でも、チームのみんなに聞いたりして2ヶ月くらいとりくんでいたら、ある日突然気付くことができたんです!覚えるときのめんどくささを遥かに上回る関数型プログラミングの最大のメリットに。
それは一言でいうなら
「書いて極悪、読んで地獄のループ地獄からの卒業」
です!
なんと、書いているうちに頭が混乱し、他人のを読んだらたちまち路頭に迷うでお馴染み「多重ループ」から卒業できるんです!
それどころか、「1重ループ = ただのループ」すら書かずにプログラミングできるんですよ!
関数型プログラミングのよくわからん用語…参照透明性とか、高階関数とか、カリー化とか…そういう言葉で表されるほぼすべての考えが、このためにあると言っても過言ではない気がします。
そして、書いてみて気づいたのは、実践レベルでとりあえず大事なのは「高階関数」だけで、あとは、よりイイ感じにするための手段なんだな。ということです。
(細かくいうとカリー化された関数も高階関数の一種なのですが今回は取り扱いません)
2つの数を掛けあわせる。それを繰り返す。本当にそれだけ?
まあ、言ってるだけではなんですので、実際にやってみましょう。
題材はプログラマ新人研修でお馴染み(?)、「かけ算九九表をつくる」です。これで2重ループを習得した方は多いのではないかと思います。
少なくとも私はそうでした。
3重ループになっても4重ループになっても、考え方は同じですので、とりあえずこれでいきます。
使用する言語はJavaScript
インプットは2つの配列
var array_a = [1,2,3,4,5,6,7,8,9];
var array_b = [1,2,3,4,5,6,7,8,9];
です。
これをもとにかけ算九九表である2重配列
[[1,2,3,4,5,6,7,8,9],
[2,4,6,8,10,12,14,16,18],
[3,6,9,12,15,18,21,24,27],
[4,8,12,16,20,24,28,32,36],
[5,10,15,20,25,30,35,40,45],
[6,12,18,24,30,36,42,48,54],
[7,14,21,28,35,42,49,56,63],
[8,16,24,32,40,48,56,64,72],
[9,18,27,36,45,54,63,72,81]]
を出力する処理を書いてみます。
まず、普通に、いわゆる手続き型で書いてみましょう。
var array_99 = [];
for(var i = 0; i < array_a.length; i++ ){
var array_dan =[];
for(var j = 0; j < array_b.length; j++) {
array_dan.push(array_a[i] * array_b[j]);
}
array_99.push(array_dan);
}
console.table(array_99);
出力はこちら
こんな感じでしょうか。
カウンタの値を直接計算せずに、配列にしている理由は、実際にループを組む場合、DBなり、入力値なり他から来た配列を処理することが圧倒的に多いからです。
さて、上のコードではそもそものarray_aとarray_bの他に4つの変数が定義されていました。
・99の結果を1段ずつ入れるための配列(array_99)
・◯の段を入れるための配列(array_dan)
・カウンタ2つ(i,j)
そして配列の中のひとつひとつの値array_a[i]とarray_b[j]もカウンタとは別に状態が変化します。
そのためこれも変数扱いして2つ追加しましょう。合計6個です。
つまり、入力と出力以外に6つの値をプログラマが理解する必要があります。6つ!
1×1 1×2 ...... という感じで 2つの数を掛け合わせる処理を繰り返しているだけなんだから、2つでいいはずなのになぜか6つ!
繰り返しはプログラムを簡単にするためじゃなかったのか…ってなりますよね…。
高階関数「map」だけで切り抜ける!なんちゃって関数型プログラミング
では、関数型プログラミングで上のコードを書き直します。言語は相変わらずただのJavaScriptです。
今出回っているほとんどのブラウザで動きます。
var array_99 = array_a.map(function(a){return array_b.map(function(b){return a * b;});});
console.table(array_99);
以上です。console出力入れて2行。
あ、本当に動きますよ、出力はこれです。
これはまとめて
console.table(array_a.map( function( a ){ return array_b.map( function( b ){ return a * b; } ); } ) );
とできるので、なんと1行!
そして、forがどこにもない!あのいやなループが消えました!バンザイ!!
さて、シンプルとはいえ、わかりづらいのでforの代わりに出てきた要素を気にして読んでいきましょう。
とりあえず目を引くのは
array_x.map
function( x ){ return... }
の2つだと思います。これをarray_aとbそれぞれ繰り返しています。元のプログラムでもfor をarray_aとbそれぞれ繰り返していました。
ということは、この2つでループ1回分を表しているんではないか…と想像できます。
ためしに上の2つを使ってループ1回分の処理をしてみましょう。
var array_a = [1,2,3,4,5,6,7,8,9];
それぞれに数字3を足して
[4, 5, 6, 7, 8, 9, 10, 11, 12]
という配列を出力してみます。
console.log( array_a.map( function( a ){ return a + 3; } ) );
配列array_aの中にそれぞれの値に、無事に3を加えることができました!
出力に使うconsole.logを消去して、純粋な処理だけをみてみます。
array_a.map( function( a ){ return a + 3; } );
もっとわかりやすくarray_aの中身をベタ書きしてみます。
[1,2,3,4,5,6,7,8,9].map( function( a ){ return a + 3; } );
さて、察しのいい方はお気づきでしょうが「map」はArrayオブジェクトの関数(メソッド)です。
べつに私が用意した「array_a」が特別なのではなく、JavaScriptの配列ならどんなものでもmapを使うことができます。
そして関数「map」の引数は
function( a ){ return a + 3; }
「引数aを取り、a+3を戻り値として返す関数」です。
つまり、関数「map」は「元の配列内の値それぞれに、引数で渡された関数の処理を適用した新しい配列を返す」関数ということになります。
これってつまりループを勝手にやってくれる関数ということですよね?
もう一度掛け算九九を出すコードを見てみましょう。
console.table(array_a.map( function( a ){ return array_b.map( function( b ){ return a * b; } ); } ) );
いわゆる2重ループなので、関数「map」を2回使ってますね。最初の配列のmap関数の中で、次の配列の「map関数」を呼び出しています。
さて、変数を数えてみましょう。
・配列array_aの値のうちひとつが入っている"a"
・配列array_bの値のうちひとつが入っている"b"
なんと2つだけです!2つの値を掛けあわせているので、変数も2つ!
「結果を入れるための配列」「カウンター」といった、処理に関係ない変数がなくなりました!
ついでにプログラムも1行です!
処理内容を関数の形で書くのが若干めんどくさいですが、気にする変数が減ったぶん、かなり楽になったはずです。
さて、そんな優れものの「map」は、「元の配列内の値それぞれに、引数で渡された関数の処理を適用した新しい配列を返す関数」でしたよね?
と、ここで突然、サブタイトルにあった「高階関数」の説明をwikipediaで見てみましょう
高階関数(こうかいかんすう、英: higher-order function)とは、関数(手続き)を引数にしたり、あるいは関数(手続き)を戻り値とするような関数のことである。
https://ja.wikipedia.org/wiki/%E9%AB%98%E9%9A%8E%E9%96%A2%E6%95%B0
関数「map」は関数を引数とする関数=高階関数でした!この説明だけだと何が嬉しいのかよくわかりませんが、今なら嬉しさが理解できるはずです。
・高階関数「map」は、配列のそれぞれの要素に対して処理を行う(ループ)
・処理の内容は関数の形で渡してあげる
これによって生まれるメリットは「面倒くさいループを書かなくていい」でした。
以上、「JavaScriptで実践!なんちゃって関数型プログラミングで九九表を作ってみよう!」でした!
JavaScriptの関数定義とaltJS
…でおわりたいところですが、上記map関数というか「Javascriptでの関数型プログラミング」のデメリットを敢えてあげるならば、
「関数を定義するのがめんどくさい、見た目がなんかごちゃっとする」ですね。
function( a ){ return a + 3; }
とか、結構な文字数です。今回取り上げた
console.table(array_a.map( function( a ){ return array_b.map( function( b ){ return a * b; } ); } ) );
もやっぱりなんか長い。
ところが!世の中にはaltJSという便利なものが存在します。これは、JavaScriptを簡単に書くことができる仕組みの「ジャンル名」です。
altJSで書いたコードが、JavaScriptのコードに変換されるので、それを実行します。
世の中にはいろいろなaltJSがあって、そのなかのひとつである「CoffeeScript」を使えばさっきのコードは、
もとの配列の準備も含めて
array_a = [1..9]
array_b = [1..9]
console.table(array_a.map((a)-> array_b.map((b)-> a * b)))
と書けますし、弊社で採用している、関数型プログラミング向きの高階関数が大量に用意された「LiveScript」では
array_a = [1 to 9]
array_b = [1 to 9]
console.table array_a |> map -> array_b |> map (*it)
と書けます。これ全部
var array_a = [1,2,3,4,5,6,7,8,9];
var array_b = [1,2,3,4,5,6,7,8,9];
var array_99 = [];
for(var i = 0; i < array_a.length; i++ ){
var array_dan =[];
for(var j = 0; j < array_b.length; j++) {
array_dan.push(array_a[i] * array_b[j]);
}
array_99.push(array_dan);
}
console.table(array_99);
と同じ結果ですからね!
altJSについてこのエントリでは説明できませんでしたが、是非使ってみてください!
CoffeeScript
http://coffeescript.org/
LiveScript
http://livescript.net/
それでは!
-
お問い合わせ
SiTest の導入検討や
他社ツールとの違い・比較について
弊社のプロフェッショナルが
喜んでサポートいたします。 -
コンサルティング
ヒートマップの活用、ABテストの実施や
フォームの改善でお困りの方は、
弊社のプロフェッショナルが
コンサルティングいたします。
今すぐお気軽にご相談ください。
今すぐお気軽に
ご相談ください。
(平日 10:00~19:00)
今すぐお気軽に
ご相談ください。
0120-315-465
(平日 10:00~19:00)
グラッドキューブは
「ISMS認証」を取得しています。
認証範囲:
インターネットマーケティング支援事業、インターネットASPサービスの提供、コンテンツメディア事業
「ISMS認証」とは、財団法人・日本情報処理開発協会が定めた企業の情報情報セキュリティマネジメントシステムの評価制度です。