Node.js の Windows へのインストールと npm の使い方
今回は Node.js の Windows 環境におけるインストールと、 そのパッケージ管理ツールである npm の使い方についてです。
Node.js とは
JavaScript は、もともとブラウザー側で解釈して、実行するクライアントサイドの言語です。Node.js を使うとローカル上で JavaScript を実行できるようになります。 これの何がいいかというと、 Node.js によりサーバーサイドの言語も JavaScript で書けるようになり、 サーバー、クライアントサイドともに同じ言語で開発できるようになります。
また、JavaScript の解析には Chrome の V8 エンジン を使っていて、動作速度はスクリプト言語の中では比較的、高速な方です。
なお、 Node.js は一時期、開発が停滞していて、業を煮やした人たちによって Io.js として分離していました。 しかし、今ではめでたく統合されています。
Node.js のインストール
まず、 Node.js のダウンロードページから Windows 用のインストーラー(node-vX.X.X-x86|x64.msi) をダウンロードします。
ダウンロードしたインストーラーを実行すれば、 Node.js はインストールが開始されます。
基本的にデフォルトのままでも問題ありません。 変えるとしたら、インストール先ぐらいでしょう。

インストールするものもカスタマイズできますが、全部いれておいた方がいいです。

Node.js の使い方
インストールが完了したら、動くか確認してみます。Node.js の実行ファイルは node.exe です。
node.exe や次章の npm.exe はインストール時のカスタムで変更していなければ、 環境変数 PATH が通っているので、そのまま使えます。
また、 Node.js をインストールするとスタートメニューに [Node.js command prompt] ができるので、このコマンドプロンプト上で実行することもできます。

とりあえず動くことを確認するには -v オプションでバージョンが表示します。
> node.exe -v v5.4.0また、 -h でヘルプが表示されます。
> node.exe -h
スクリプトファイルの実行
node の基本的な使い方としては JavaScript ファイルを渡して実行します。hello.js :
console.log("Hello world!");
> node.exe hellol.js Hello world!
対話モード (REPL)
node の後にスクリプトファイルを指定しないと対話モードで起動します。> node > console.log("Hello world!"); Hello world! undefined > .exit
デバッグモード
node の引数に debug を渡して、その後にスクリプトファイルを書くとデバッガーが起動します。hello_debug.js :
console.log("Hello "); debugger; console.log("world!");
> node debug hello_debug.js debug>connecting to 127.0.0.1:5858 .< Debugger listening on port 5858 debug> . ok debug>break in d:\home\programmers_notes\node\hello_debug.js:2 1 > 2 console.log("Hello "); 3 debugger; 4 console.log("world!"); debug> next debug> debug>< Hello debug>break in d:\home\programmers_notes\node\hello_debug.js:3 1 2 console.log("Hello ");> 3 debugger; 4 console.log("world!"); 5 debug> cont debug> debug>break in d:\home\programmers_notes\node\hello_debug.js:4 2 console.log("Hello "); 3 debugger;> 4 console.log("world!"); 5 6 debug> quit
npm の使い方
Node.js には多くのパッケージが公開されており、 npm を使ってインストールなどの管理が出来ます。npm は最初の引数をコマンドとして種々の操作を行います。取り得るオプションや引数はそのコマンドによって変わってきます。
> npm コマンド [オプション] [引数]
プロキシ環境での設定 (config)
npm では Web からパッケージを取得してインストールします。 そのため、ネットワークの接続にプロキシを使用している場合は サーバーを設定していないとエラーとなります。プロキシサーバーを設定するには以下のコマンドを実行します。
> npm config -g set proxy プロキシサーバーのアドレス:ポート番号 > npm config -g set https-proxy プロキシサーバーのアドレス:ポート番号
> npm config -g set proxy http://foo.co.jp:8080 > npm config -g set https-proxy http://foo.co.jp:8080
プロキシを設定してもまだエラーが発生する場合には さらにレジストリーの参照先も変更する必要があります。
> npm config -g set registry http://registry.npmjs.org/ここで指定している -g オプションは全ユーザーに対して設定するためのものです。 -g をつけていないと実行しているユーザーのみの設定となります。
設定した内容を確認する場合は set ではなく ls(list) を指定します。
> npm config ls参考 :
プロジェクトの作成 (init)
Node.js のプロジェクトを作成する場合、 init のコマンドでプロジェクトの設定ファイル(package.json)ができます。> npm init [-y]-y (--yes) をつけるとすべてデフォルトの値を使って作成します。 つけていない場合は対話的に設定内容を入力することになります。
例えば、 sample フォルダーを作成し、そこで init -y を実行すると以下のファイルが作成されます。
package.json :
{ "name": "sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }各項目の詳しい説明については以下のサイトで記述されています。
パッケージのインストール (install)
パッケージのインストールは install(i) コマンドを使用します。> npm install [-g] [パッケージ名]パッケージ名でインストールするパッケージを指定します。 パッケージは最新版がインストールされますが パッケージ名@バージョン(foo@1.2.3) とするとバージョンを指定することができます。
-g (--global) はグローバルインストールのオプションです。
Gulp や JSHint のようにコマンドライン上で起動させるものはグローバルインストールにします。
通常のパッケージの場合は -g を付けません。この場合、カレントのプロジェクトに node_modules のフォルダーを作って、インストールされます。
また、 パッケージ名を省略するとカレントのプロジェクトで利用しているパッケージをまとめてインストールします。 こちらはダウンロードや Clone でプロジェクトを取得した際などに使います。
パッケージのアップデート(install, update)
パッケージのアップデートも install コマンドで行うことができます。例えば npm 自体をアップデートするには以下のコマンドを実行します。
> npm install -g npmまた、 update を使うとまとめてアップデートができます。 install と同様に -g オプションをつけるとグローバルインストールしたパッケージ、つけない場合はプロジェクトローカルのパッケージが対象です。
> npm update > npm -g update
パッケージの利用
パッケージをインストールすると require で呼び出して使うことができるようになります。index.js :
var lodash = require('lodash'); var output = lodash.without([1, 2, 3], 1); console.log(output);
> npm install lodash > node index.js [ 2, 3 ]ちなみに package.json の main でスクリプトファイルが指定されているパッケージの場合、 require で呼び出された際に、最初にそのファイルが実行されます。
依存パッケージの登録
パッケージ名の指定なしの npm install でインストールされるパッケージはそのプロジェクトが依存しているパッケージです。 依存パッケージは以下のコマンドでパッケージに登録(package.json に書き込み)できます。> npm install パッケージ名 --save > npm install パッケージ名 --save-dev--save-dev の方は開発やテストでのみ使用するパッケージに対して使います。
スクリプトの実行 (start, test, ...)
package.json の "scripts" フィールドの start や test などの項目に処理を記述すると npm から実行できるようになります。> npm start > npm test
例えば、 package.json に以下の記述を加えると npm start で index.js が実行されます。
"scripts": { "start": "node index.js" },
> npm start > sample@1.0.0 start c:\Users\...\sample > node index.js [ 2, 3 ]
その他のコマンド
その他のよく使いそうなコマンドもいくつかあげておきます。なお、 -g オプションは install の場合と同じで、対象がグローバルとなります。
コマンドの書式 | 別名 | 説明 |
---|---|---|
npm -h npm コマンド -h npm help 用語(コマンド) |
ヘルプの表示。 help コマンドの場合はブラウザーで説明のページを開く。 | |
npm uninstall [-g] パッケージ名 | remove, rm, r, un, unlink | パッケージのアンインストール |
npm ls [-g] npm ls [-g] パッケージ名 | list, la, ll |
インストールしたパッケージのリストの表示。 パッケージ名を指定するとそのパッケージの情報だけ表示。(バージョンを確認したい場合など) |
npm view パッケージ名 | info, show, v | パッケージの詳細情報の表示 |
lodash を使った JavaScript における関数型のデータ処理
「関数型のデータ処理」と呼んでいるのは、
高階関数を使い、遅延評価で行うデータ処理の手法で、
関数型プログラミングでよく使われます。
これは関数型プログラミングでなくても、役に立つ機能であり、
JavaScript でも lodash というライブラリーを使えば、これが行えるようになります。
今回はこの関数型のデータ処理の魅力と lodash でのやり方について紹介したいと思います。
関数型データ処理の魅力
関数型のデータ処理方法は関数型プログラミングでなくても、 役に立つため、関数型言語以外の言語でも積極的に取り入れられるようになって来ました。この辺りのことは最後に説明するとして、 このデータ処理方法の何がよいかというと 短くかつ分かりやすいコードが書けることです。
説明のために、サンプルを挙げてみます。
カンマ区切りの文字列があったとします。まずこれを配列に分割し、 次の処理を行います。
- 要素を数値に変換
- 奇数の要素のみ取得
- 全ての要素の積を算出
従来方式 Case 1
まずは単純に処理を順に行う方法で書いてみます。// 元の配列 [ '3', '2', '9', '6' ] var src = "3,2,9,6".split(","); // 数値に変換 [ 3, 2, 9, 6 ] var map_ary = []; for (cnt in src) { map_ary.push(parseInt(src[cnt], 10)); } // 奇数を取得 [ 3, 9 ] var filter_ary = []; for (cnt in map_ary) { if (map_ary[cnt] % 2 == 1) { filter_ary.push(map_ary[cnt]); } } // 要素の積算 var prod = 1; for (cnt in filter_ary) { prod *= filter_ary[cnt]; } console.log(prod); // 27
従来方式 Case 2
処理を順にやるだけだと流石に無駄が多いので、ループが一回で済むようにコードを直してみます。var src = "3,2,9,6".split(","); var prod = 1; for (cnt in src) { var num = parseInt(src[cnt], 10); // 数値に変換 if (num % 2 == 1) { // 奇数を取得 prod *= num; // 要素の積算 } } console.log(prod); // 27
lodash を用いた方法
処理の内容については後から説明しますが、とりあえず lodash を使って書き直してみます。var src = "3,2,9,6".split(","); var prod = _(src) .map(str => parseInt(str,10)) // 数値に変換 .filter(n => n % 2 == 1) // 奇数を取得 .reduce((t,n) => t * n); // 要素の積算 console.log(prod); // 27lodash を使った書き方では Case 1 のように分かりやすく順に書いていくことができる上、 短く書けます。
それでいて遅延評価の機能によって、実は Case 2 のように効率的に処理しています。
ここで、ついでに何故この処理方法が関数型プログラミングでよく使われるかを説明したいと思います。
関数型では 参照透過性 のため、一度作ったオブジェクトは変更できません。 これは並列処理に強くなるというメリットがあるのですが、 変数の値を変えられないので、ループを回すことができないといった縛りがでてきます。
そのため、高階関数を使ったデータ処理は関数型には必須の機能となります。
lodash
lodash とは
lodash というのは、汎用的に使える機能を集めた JavaScript のライブラリーで、 特に今回紹介する関数型のデータ処理に重きが置かれています。もともと Underscore.js というライブラリーがあったのですが、 意見の相違が原因で分離して開発されることになりました。
Node.js と Io.js のように統合の動きもありますが、こちらの統合はなかなか難しいそうな感はあります。 Underscore.js と lodash は似たような機能を持っていますが、 パフォーマンスを考慮して、 遅延評価を導入しており、個人的には lodash の方がお勧めだと思います。
クライアントサイドでの使用
クライアントサイドで有名な JavaScript のライブラリーを使う場合、 CDN (Contents Delivery Network) といって、公開されているスクリプトファイルを利用するのが、お手軽です。CDN には Google, Microsoft のものや cdnjs, jsDelivr などいくつかあるのですが、 ここでは cdnjs のアドレスを紹介します。
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.min.js"></script>上記のどちらかを html に書いておけば、使えるようになります。 なお、 min とつけている方はコアの機能だけのものです。
使用する場合、もとになった Underscore.js のように lodash では"_"(アンダーバー)を使ってアクセスします。
ちょうど Prototype.js や jQuery が $ を使うような感じです。
使用例 :
Node.js での使用
Node.js で使用する場合は npm コマンドでインストールします。$ npm install lodashインストールしたプロジェクトでは require でロードすれば、使えるようになります。
var _ = require('lodash');require の戻り値を格納する変数の名前は何でもよいのですが、慣例に沿って _ にしています。
ただ、ちゃんとプロジェクトにインストールするにはもう少し手順が必要なので、サンプルコードを例に簡単に手順を紹介します。
まず、適当なディレクトリーを作ってそこにサンプルのファイルをおきます。ファイルは以下のリンクからダウンロード(リンク先を保存)してください。 その後、プロジェクトの作成とインストールを行います。
$ npm init -y $ npm install lodash --saveサンプルを実行は node コマンドで行います。
$ node lodash_sample.js各コマンドの詳しい説明に関しては、以前の記事を見て下さい。
基本的な関数
ここから関数型のデータ処理で重要な 5 つの機能について説明します。- 逐次処理(forEach)
- 写像(map)
- フィルター(filter)
- 畳み込み、縮退(reduce)
- 並び替え(sortBy)
forEach : 逐次処理
データ処理の中でもっとも基本的な処理は逐次処理です。これには forEach 関数を使います。 引数に関数を渡すことによって、その関数をコレクションの各メンバーに適用していきます。
以下の例では配列の要素を 1 つづつ出力します。
_.forEach([3,2,9,6], function(elem) { console.log(elem);});関数の最初の引数に対象となるデータを渡しています。
forEach は一番最初にあげたサンプルのように書くこともできます。 関数を連結する場合に使うのですが、関数の連結は少し注意点があるので、主要な関数の紹介の後に説明したいと思います。
_([3,2,9,6]).forEach(function(elem) { console.log(elem);});逐次処理は for ... in と同じようなものです。
ただ、 JavaScript の for ... in は他の言語と違い、ループの変数が数値のインデックスであり、 かなり不自然です。 それに対し、 forEach と高階関数を使えば、各要素を変数として受け取ることになり、 分かりやすいコードになるのではないかと思います。
map : 写像
写像というのは map, mapping とも呼ばれる処理で、 配列などのコレクションのメンバーに一つずつ関数を適用して 戻り値で新しいコレクションを作ります。
lodash では、そのまま map という関数名です。
_.map([3,2,9,6], function(elem) {return elem*2;}); // [ 6, 4, 18, 12 ]map に渡す関数は、各要素を引数にとる関数で、その戻り値が新しいコレクションの要素となります。 最初のサンプルのように渡す関数の戻り値の型を変えれば、 型を変えた新しいコレクションを作ることもでき、用途の広いメソッドです。
forEach, map などには渡す関数は、通常の名前付きの関数でもいいですし、 サンプルのような無名関数 でもかまいせん。
また、無名関数は ES6 から アロー関数 というより短い形式で書くこともできるようになっています。
_.map([3,2,9,6],
elem => elem*2 );
これは function という単語や return を書く必要がなくなり便利です。
以降の例ではアロー関数を使った書き方にしたいと思います。CoffeeScript や C# などを使ったことのある人には馴染みのある書き方だとは思いますが、 より詳しく知りたい方は以下のサイトを見てください。 また、引数の数が違う関数やオブジェクトのメソッドをデータ処理に使いたい場合は次の記事を参考にしてください。
filter : フィルター
フィルターはコレクションの中から条件にあう要素を取り出す処理です。データ処理で重要なメソッドをさらに絞るとすると、前節の map とこのフィルターが、 特に重要度が高いです。

これも、そのまま filter という名前になっています。
_.filter([3,2,9,6], elem => (elem % 2) == 1);
データ処理に渡すのは基本的に関数です。 ただ、 JavaScript ではコレクションの要素がオブジェクト(連想配列)となることが多いせいか、 lodash では filter のように "条件を返す関数" を引数にとる関数では、 特殊な書き方ができます。
関数ではないものを渡すと matches, matchesProperty が使われるようになっています。
var objs = [{'name': 'Tanaka', 'age': 35, 'enrolled': true}, {'name': 'Yamada', 'age': 30, 'enrolled': false}, {'name': 'Satou', 'age': 41, 'enrolled': true}]; // matches 呼び出し _.filter(objs, {'age': 35, 'enrolled': true}); // [ { name: 'Tanaka', age: 35, enrolled: true } ] // matchesProperty 呼び出し _.filter(objs, ['age', 35]); // [ { name: 'Tanaka', age: 35, enrolled: true } ]さらに bool 値のプロパティーを指定すると、要素のプロパティーの値が条件として使用されます。
_.filter(objs, 'enrolled'); // [ { name: 'Tanaka', age: 35, enrolled: true }, // { name: 'Satou', age: 41, enrolled: true } ]
reduce : 畳み込み(縮退)
畳み込みもしくは縮退は 要素を順に取得し、それらを計算に使った結果を返す処理です。
畳み込みは他言語では fold, reduce, inject などの関数名が使われます。 lodash では縮退の方の reduce という関数名です。
var src = [3,2,9,6]; _.reduce(src, (sum, elem) => sum + elem); // 20 _.reduce(src, (max, elem) => (max < elem) ? elem : max); // 9reduce に渡す関数は 2 つの引数を取り、 2 つ目が各要素で、 関数の戻り値が次に呼ばれた時の 1 つ目の引数です。 これにより、各関数の結果を重ねていったものが reduce の戻り値として得られます。
合計の例を順に記述すると次のようになります。
{3, 2, 9, 6} (3, 2) => 3 + 2 ↓ (5, 9) => 5 + 9 ↓ (14, 6) => 14 + 6 ↓ 20渡した関数は 2 つの目の要素から呼ばれ、最初の引数 1 つ目の要素になります。 これは初期値も引数として指定して、最初の要素から処理することもできます。
_.reduce([3,2,9,6], (count, elem) => count+1, 0); // 4map, filter がコレクションから新しいコレクションを返す関数の基本なのに対し、 reduce はコレクションの各要素から値(スカラー値)を算出する関数の中でもっとも基本的な関数です。
sortBy : 並び替え
sortBy は要素を並び替えた新しいデータを返す関数です。ソートでは比較関数を渡すのが他の言語では一般的なのですが、 sortBy は By とついているように比較するためのプロパティーを指定します。
var objs = [{'name': 'Tanaka', 'age': 35}, {'name': 'Yamada', 'age': 30}, {'name': 'Satou', 'age': 41}]; // age の値でソート _.sortBy(objs, 'age'); // { name: 'Yamada', age: 30 }, // { name: 'Tanaka', age: 35 }, // { name: 'Satou', age: 41 } ]比較に用いる値はプロパティー名での指定だけでなく、関数を渡して決めることもできます。 これを使えば、逆順ソートなどもできます。
// age で逆順ソート _.sortBy(objs, elem => elem.age * -1); // [ { name: 'Satou', age: 41 }, // { name: 'Tanaka', age: 35 }, // { name: 'Yamada', age: 30 } ]次の例は最初に紹介すると分かりづらいかなと思って、ここに持ってきました。 sortBy は比較に使う関数を渡すのではなく、比較に使う値を決める関数を渡す ということをおさえていると渡す関数の定義の仕方が理解しやすいと思います。
var strs = ["foo", "bar", "BAZ", "qux"]; _.sortBy(strs); // [ 'BAZ', 'bar', 'foo', 'qux' ] _.sortBy(strs, elem => _.toLower(elem) ); // [ 'bar', 'BAZ', 'foo', 'qux' ] // _.sortBy(strs, _.toLower); でも可また、一つのプロパティーの値だけでは同じ値になってしまうような場合、 複数指定することもできます。
var points = [{'x': 2, 'y': 8}, {'x': 5, 'y': 1}, {'x': 2, 'y': 4}]; // x でソートし、x が同じなら y でソート _.sortBy(points, ['x', 'y']); // [ { x: 2, y: 4 }, { x: 2, y: 8 }, { x: 5, y: 1 } ]
その他の関数
データ処理の定番の関数以外で、抑えておいた方がいいかなと思うものも挙げて置きます。- size
- 要素数の取得
- find, findLast
-
指定した条件に最初(最後)にマッチする要素の検索。
検索もデータ処理ではよく行う処理ですが、 条件にあうすべての要素を取得するのがフィルター(filter)で、 最初の要素を取得するのが find です。_.find([3,2,9,6], elem => (elem % 2) == 1); // 3 _.findLast([3,2,9,6], elem => (elem % 2) == 1); // 9
- includes
-
要素を含んでいるかの判定。
_.includes([3,2,9,6], 2); // true
- some, every
-
要素どれか一つ(すべて)が条件を満たすかどうかの判定。
_.some([3,2,9,6], elem => (elem % 2) == 1); // true _.every([3,2,9,6], elem => (elem % 2) == 1); // false
result = _.size([3,2,9,6]); // 4
Array はそのまま Array のオブジェクトですが、対象が Collection の場合、 Array だけでなく配列のように振る舞うデータ構造すべてに使えます。
なお、今回の記事では対象が Collection の関数だけ紹介しています。
メソッドの連結と遅延評価
メソッドの連結
関数の連結を行う場合、最初に配列(Array)を lodash のオブジェクト(_) に引数として渡すか、 chain 関数に渡してから連結します。_.chain([3,2,9,6]) // _([3,2,9,6]) と同じ .filter(elem => (elem % 2) == 1) .map(elem => elem * 2) .value(); // [ 6, 18 ]最初の _() や chain() の関数が返すオブジェクトはシーケンス(Seq)です。
シーケンスの filter メソッドが返すオブジェクトもシーケンスなので、 さらに map で連結できるようになっています。 関数の連結はシーケンスのメソッドの連結(method chain)によって実現されています。
ただ、このシーケンスは配列ではありません。 最後に forEach などで逐次処理したり、 reduce で値の算出に使う場合はいいのですが、 新しく作った配列を欲しい場合は value で配列に戻す必要があります。
遅延評価
関数を連結して書くとメモリーがもったいないと思われる人もいるかもしれません。 しかし、一番最初の例にあげたように内部的には Case 2 のように効率的に処理しています。これを実現しているのが 遅延評価(Lazy evaluation) の機能です。
連結時のシーケンスは 必要になるまで実行(評価)されないようになっています。 前節の例では value で配列に戻すところが実行のタイミングです。
連結の処理は一見、次のように処理してるように見えます。
filter map {3, 2, 9, 6} → {3, 9} → {6, 18}しかし、実際には遅延評価により、メソッドごとに結果をためるのではなく、 1 要素ずつ流すように次のメソッドに渡していきます。
filter map 3 → 3 → 6 2 → ☓ 9 → 9 → 18 6 → ☓遅延評価の効果をもっとわかりやすくみたいという方は次のサイトがお勧めです。
英語の記事ですが、画像(GIF 動画)をみているだけも理解が深まると思います。 ちなみに画像に出てくる take は先頭から指定した数だけ要素を取り出すメソッドです。
なお、基本処理にあげた sortBy は遅延評価において少し特殊です。
ソートという処理を考えたらわかると思いますが、 ソートは全要素が揃わないと完了しない処理です。 そのため、 sortBy に関しては流れるように次に渡すのではなく、 そこで一旦要素が溜められることになります。
プログラミング言語の傾向
最後に lodash で遅延評価、高階関数を使った関数型のデータ処理について、 最近のプログラミング言語での流れを紹介します。関数型言語
高階関数を使ったデータ処理は LISP で古くから行われてきた手法です。 ただ、遅延評価や参照透過性といった考え方がなく、 最近では LISP は関数型言語には入れないことが多いです。しかし、 Haskell, Scala, F# などの最近の関数型言語では、 遅延評価、高階関数を使ったデータ処理は基本的な機能としてあります。
LISP も今の意味で関数型といえる Closure が出てきています。 特に純粋関数型言語である Haskell は特定のデータ処理だけでなく、 全体が遅延評価されるようになっています。
関数型以外の言語
高階関数を使ったデータ処理はコードを短くかつわかりやすく記述することができます。 そのため、 Ruby, Python をはじめ多くの言語で取り入れられてきました。 特に C# の LINQ では遅延評価もできます。 Java も Java 8 では Stream ができ、 C++ でさえ C++14 以上であれば関数型のデータ処理ができる Streams のライブラリーが使えますこれからのプログラミングでは一般的な機能になって来ているようです。
- C# やるなら LINQ を使おう | プログラマーズ雑記帳
- Java Streams, Part 1: java.util.stream ライブラリー入門
- C++14 Streams を使った関数型のデータ処理 | プログラマーズ雑記帳
特にコードブロックは高階関数を使ったデータ処理としてはもっともエレガントに書ける記述スタイルではないかと思っています。 ただ Ruby の遅延評価は Ver. 2.0 から導入された機能で、後付な感じがちょっと残念です。
しかし、 Ruby の作者である Matz(まつもとゆきひろ) さんが、 このデータ処理を全面に押し出した関数型言語の stream を公開されています。(まだプロトタイプですが)
- Rubyist Magazine - 無限リストを map 可能にする Enumerable#lazy
- GitHub - matz/streem: prototype of stream based programming language
JavaScript と altJS
話を JavaScript に戻すと、 ES6 では Array オブジェクトに map, filter といったメソッドが追加され、 lodash 抜きでも高階関数を使ったデータ処理ができるようになっています。 ただ、遅延評価については特に書いてないようなので、 実装にもよりますが、おそらくできないでしょう。となってくると、 CoffeeScript のような altJS で自然に書いて、 JavaScript に変換するときに lodash を使ってくれないかなと思います。
実際、そういった言語はあります。それが RedScript で、 以前に RedScript の紹介記事を書いた時はまだまだな言語でしたが、最近では完成度が上がってきた感じです。
(なお、 redscript.org のアカウントがカードローン系のサイトに取られてしまったみたいなので、 検索するときには注意してください)
- RedScript : Ruby 風の JavaScript 変換言語 | プログラマーズ雑記帳
- GitHub - AdamBrodzinski/RedScript: A Ruby Flavored Language
lodash を使った JavaScript でのカリー化と部分適用
関数型プログラミングにはカリー化と呼ばれる手法があります。
これは lodash などを用いた高階関数によるデータ処理を行う際に役に立ちます。また、 lodash ではカリー化を簡単に行える関数も用意されています
今回は JavaScript でのカリー化と lodash を使った実現方法について説明します。
また、カリー化と似た部分適用、 バインディングについても説明しています。
カリー化
カリー化というのは複数の引数をとる関数を、引数が "もとの関数の最初の引数" で戻り値が "もとの関数の残りの引数を取り結果を返す関数" であるような関数にすることことを言います。
これは言い換えると
2 つ以上の引数を持つ関数を 1 つの引数の関数の組み合わせになおすともいえます。
実際に例を見たほうが分かりやすいと思うので、次のような 2 つの引数を取る関数をカリー化してみます。
function plus(a, b) { return a + b; }カリー化は戻り値として無名関数を返すことによって実現します。
function curried_plus(a) { return function(b) { return plus(a,b); }; }戻り値の関数内では関数内でローカルな引数 a を使っていますが、 クロージャーという技術によって、うまいこと残してくれるようになっています。
plus(2, 3)
と書いていたものが curried_plus(2)(3)
と書けるようになりました。これがカリー化です。
plus(2,3); // 5 curried_plus(2)(3); // 5カリー化に似たものに 関数の部分適用 という用語もあります。
こちらは
複数の引数を持つ関数の一部の引数にだけに実引数を適用する操作です。
先ほどの plus の関数の引数の一つを 2 に固定した関数を作ってみます。
function partial_plus(b) { return plus(2, b); } partial_plus(3); // 5この partial_plus という関数を作る操作が部分適用です。
ざっくり書くと次のような感じです。
カリー化 : f(a, b, c) → g(a)(b)(c) 部分適用 : f(値1, 値2, c) → g(c)
カリー化の用途
カリー化、部分適用の説明をしましたが、私は実用主義ですので、実は言葉の定義とかあまり興味ないです。大事なのは「それがなんの役に立つか?」というところでしょう。
カリー化が役に立つ場面は 高階関数を使ったデータ処理 を行う時です。
この処理方法が非常に重要なのですが、そちらについては前回の記事に書いているので、そちらを見て下さい。 高階関数を使ったデータ処理に渡す関数の多くは引数が一つだけです。 それに対して、複数の引数をとる関数を適用しようとすると次のようになります。
_.map([3,2,9,6], function(e) { return plus(2, e); }); // [ 5, 4, 11, 8 ]これはこれで問題は無いのですが、カリー化した関数があると次のように書けます。
_.map([3,2,9,6], curried_plus(2));
このように、要求される関数の形式と使いたい関数の引数の数が違う場合にカリー化は使えます。カリー化した関数を使う方法は見やすく、分かりやすいのではないかと思います。
とはいえ、わざわざ関数を作ったりするのはちょっと面倒です。
関数型言語である Haskell や F#(OCaml) などでは 関数は勝手にカリー化されていて、最後の引数を省略すればいいだけなので、お手軽に使えます。
JavaScript にはそんな機能はないのですが、関数型のデータ処理を進める lodash というライブラリーがあり、一発でカリー化してくれる関数が用意されています。
次章から lodash を利用した方法について解説していきます。 なお、 lodash 自体の説明は前述のリンク先の記事をご覧ください。
lodash の利用
lodash を使って カリー化(curry)、 関数の部分適用(partial)を行う方法について紹介します。また、それらと用途が似ている バインディング(bind) ついても説明しています。
curry : カリー化
lodash の curry 関数でカリー化することができます。先ほどの plus を curry を使ってカリー化してみます。
_.map([3,2,9,6], _.curry(plus)(2)); // [ 5, 4, 11, 8 ]引数が 3 つの場合は次のようになります。
function plus3(a, b, c) { return a+b+c; } var curried_plus3 = _.curry(plus3); var ary = [3,2,9,6]; _.map(ary, curried_plus3("a")("b")); // [ 'ab3', 'ab2', 'ab9', 'ab6' ] _.map(ary, curried_plus3("a", "b")); // [ 'ab3', 'ab2', 'ab9', 'ab6' ]データ処理に使いたい引数の位置が最後ではない場合、 記入子(placeHolder) を使って対象を変えることもできます。
_.map(ary, curried_plus3("a")(_, "b")); // [ 'a3b', 'a2b', 'a9b', 'a6b' ] _.map(ary, curried_plus3(_, "a")(_, "b")); // [ '3ab', '2ab', '9ab', '6ab' ]ただ、 この placeHolder は少し指定の仕方がわかりづらいです。 使いたい引数を指すのではなく、引数の場所を一つ後ろと入れ替えるようになっています。
function plus4(a,b,c,d) { return a+b+c+d; } _.curry(plus4)('a')('b')(_, 'c')('d'); // abdc _.curry(plus4)('a')(_, 'b')('c')('d'); // acbd _.curry(plus4)(_, 'a')('b')('c')('d'); // bacd
partial : 部分適用
lodash では関数の部分適用を行うための partial という関数も用意されています。これは引数を固定した関数を返します。
var partial_plus3 = _.partial(plus3, 'a', 'b'); _.map([3,2,9,6], partial_plus3); // [ 'ab3', 'ab2', 'ab9', 'ab6' ]使い分けとしては、作成した関数に名前をつけて使うのであれば、 カリー化の方が引数を変えられるので、柔軟性が高いです。
ただ、直接使うのであれば、どちらを使ってもあまりかわりません。
_.map([3,2,9,6], _.curry(plus)(2)); // [ 5, 4, 11, 8 ] _.map([3,2,9,6], _.partial(plus, 2)); // [ 5, 4, 11, 8 ]placeHolder を使う場合は partial の方が指定の仕方がわかりやすいです。
_.map([3,2,9,6], _.partial(plus3, 'a', _, 'b')); // [ 'a3b', 'a2b', 'a9b', 'a6b' ] _.map([3,2,9,6], _.partial(plus3, _, 'a', 'b')); // [ '3ab', '2ab', '9ab', '6ab' ]
bind : バインディング
そのままでは使えない関数、メソッドなどをうまく適用させることをバインディングと呼びます。引数が合わない関数を使えるようにするカリー化や部分適用もバインディングの一種なのですが、 lodash でバインディングという場合は オブジェクトのメソッドを適用させることを指すようです。
bind 関数はオブジェクトとメソッドをまとめた関数を作成します。 これを利用するとオブジェクトのメソッドをデータ処理に使えるようになります
function Person(name, age) { this.name = name; this.age = age; this.add_age = function (n) { return this.age + n; }; } var user = new Person('taro', 30); _.map([3, 2, 9, 6], _.bind(user.add_age, user)); // [ 33, 32, 39, 36 ]なお、 バインディングは lodash の Ver. 3 と ver. 4 でやり方が変わりました。 古いバージョンを使っている場合は注意してください。
サンプルコード
説明で使用したサンプルのコードは以下のリンクからダウンロード(リンク先を保存)できます。 適当なディレクトリーを作って、そこにサンプルのファイルをおき、プロジェクトの作成とインストールを行います。$ npm init -y $ npm install lodashサンプルを実行は node コマンドで行います。
$ node lodash_sample.js各コマンドの詳しい説明に関しては、以前の記事を見て下さい。