Lodashの省略記法の理解を深める

# はじめに
[filter](https://lodash.com/docs/4.17.10#filter)のように、Lodashは関数を引数とする関数を提供しています。
しかし、関数を指定すべき引数に対して、文字列や配列、オブジェクトを指定できるケースもあります。
そして、サンプルコードを読めば、それが省略記法であることは **何となく** 分かります。
本記事では、この **何となく** を **しっかり** にすることを目指します。

# 環境情報
Lodash 4.17.10

# 省略記法の例
以下はLodashのドキュメントのfilter関数のExampleです。

```javascript:filterのExample
var users = [
  { 'user': 'barney', 'age': 36, 'active': true },
  { 'user': 'fred',   'age': 40, 'active': false }
];
 
_.filter(users, function(o) { return !o.active; });
// => objects for ['fred']
 
// The `_.matches` iteratee shorthand.
_.filter(users, { 'age': 36, 'active': true });
// => objects for ['barney']
 
// The `_.matchesProperty` iteratee shorthand.
_.filter(users, ['active', false]);
// => objects for ['fred']
 
// The `_.property` iteratee shorthand.
_.filter(users, 'active');
// => objects for ['barney']
```

filterの引数は以下の二つです。

* collection: フィルタリング対象の配列またはオブジェクト
* predicate: フィルタリングの条件を表す関数

最初の例は引数の定義に忠実にしたがった呼び出しとなっています。
一方で、2つめ以降の例は `predicate` に関数ではなく、オブジェクトや配列が指定されています。
型の違いにびっくりしますが、Exampleのコメントを読めばそれが省略記法であることが分かります。
filterに限らず、他の関数（e.g., [countBy](https://lodash.com/docs/4.17.10#countBy), [find](https://lodash.com/docs/4.17.10#find)）もこのような省略記法が使用できます。

さて、この省略記法って何者でしょうか？

* 関数を引数とする関数であれば、任意の関数で使用できるのでしょうか？
* Exampleで書かれている以外の省略記法ってないのでしょうか？

ドキュメントを読んでもExampleに書かれている以上の情報は見つかりませんでした。

# Lodash 4  Cookbookの解説
何気なく検索してみると、[Lodash4 Cookbook](https://leanpub.com/lodashcookbook/read#predicate-syntax) に良さげな説明が書かれていました。

以下、 [Lodash 4 - Cookbook Predicates](https://leanpub.com/lodashcookbook/read#predicate-syntax) より引用です。
> For lodash functions which accept predicates, e.g. _.find and _.filter, predicates can be specified using functions, strings, and objects.

> If a function is provided, it’s used directly. The predicate matches if the function returns a truthy value.
If only a string is provided, it’s used to create a function using _.property as the predicate.
If an array that contains a string and a value is provided, the string and the value are used to create a function using _matchesProperty as the predicate.
If an object is provided, it’s used to create a function using _.matches as the predicate.

つまり、

* Exampleに書かれていた省略記法は、filterやfindに限らず、他のLodashの関数でも使用できる
* Exampleに書かれていたもの以外で省略記法は存在しない

と言えそうです。

# Lodashのソースコード
Lodash4 Cookbookのおかげでルールは見えてきました。
せっかくなので、ソースコードの方も覗いてみましょう。

## filter
以下はLodashのfilter関数の定義です。

```javascript:lodash.js
function filter(collection, predicate) {
  var func = isArray(collection) ? arrayFilter : baseFilter;
  return func(collection, getIteratee(predicate, 3));
}
```

引数はドキュメントに書かれている通り、 `collection` と `　predicate` です。
最初に、 `collection` が配列かどうかに応じて用いるフィルターを選択しています。これについては深入りしないでおきます。
ポイントとなるのは、 return文の中にある `getIteratee(predicate, 3)` です。

## getIteratee
getIterateeの定義を覗いてみます。

```javascript:lodash.js
function getIteratee() {
  var result = lodash.iteratee || iteratee;
  result = result === iteratee ? baseIteratee : result;
  return arguments.length ? result(arguments[0], arguments[1]) : result;
}
```

読み解き難いですが、 `lodash.js` で `lodash.iteratee = iteratee;` と宣言されているので、基本的には `result` はbaseIterateeになると思われます。
getIterateeには `predicate`と `3` が引数として渡されるので、 `arguments.length` は2です。
したがって、 `baseIteratee(predicate, 3)` が評価されるはずです。

## baseIteratee
baseIterateeの定義を覗いてみます。

```javascript:lodash.js
function baseIteratee(value) {
  // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
  // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
  if (typeof value == 'function') {
    return value;
  }
  if (value == null) {
    return identity;
  }
  if (typeof value == 'object') {
    return isArray(value)
      ? baseMatchesProperty(value[0], value[1])
      : baseMatches(value);
  }
  return property(value);
}
```

Lodash4 Cookbookに書かれていたルールに対応していそうな内容です！
引数の `value` に `predicate` が渡されることに注意します。

まず、 `value` が関数であるかをチェックし、関数ならばそのまま `value` を返しています。
これは最初のExampleにおけるルートですね。

`value` が関数でない場合、 `value` が `null` であるかをチェックしています。
`null` ならばデフォルト値であるidentity関数を返しています。
これは、Exampleにはありませんでしたが、実はLodashのドキュメントに、 `predicate` のデフォルト値が `_.identity` であると書かれています。

`value` が関数でも `null` でもない場合、　`value` の型が `object` であるかをチェックしています。
ここで、 `value` が配列であっても `typeof value` は `object` であることに注意してください。
つまり、 `predicate` に配列またはオブジェクトが指定された場合のパターンであり、2つめと3つめのExampleにおけるルートです。
このケースでは、三項演算子を用いて以下の値を返しています。

* `value` が配列の場合: `baseMatchesProperty(value[0], value[1])`
* そうでない場合: `baseMatches(value)`

`baseMatchesProperty` と `baseMatches` はそれぞれ、 `_.matchesProperty` と `_.matches` を実装している関数です。深追いはしませんが、それぞれ `_.matchesProperty` と `_.matches` で読み換えてよいかと思います。
まさにLodash4 Cookbookに書かれていたルールの通りです。

最後に、 `value` が関数でも `null` でも `object` でもない場合に、 `property(value)` を返しています。
これは `value` が文字列のパターンであり、4つめのExampleにおけるルートですね。
期待通り、 `_.property` を適用した結果を返しています。

長かったですが、以上より、Lodash4 Cookbookに書かれていたルールを確認できました！

## Lodashのソースコードのまとめ
* Lodashの関数を引数にとる関数は、その引数に対して、getIteratee, baseIterateeを適用している
* getIteratee, baseIterateeは、以下のマッピングを行う
  * 引数 `predicate` が配列の場合、関数 `_matchesProperty(predicate[0], predicate[1])` にマッピングする
  * 引数 `predicate` がオブジェクトの場合、 関数 `_matchesProperty(predicate)` にマッピングする
  * 引数 `predicate` が文字列の場合、 関数 `_.property(predicate)` にマッピングする

上記の仕組みにより、関数を引数として渡す代わりに、配列やオブジェクト、文字列を渡すことができる。また、その仕組みの核となるgetIteratee, baseIterateeはfilterのみに限らず、他の関数を引数にとる関数でも使い回されている。

# おわりに
普段、何気なく使っているライブラリもソースコードを見てみると面白いものです。
Lodashの省略記法については、公式のドキュメントでもう少し詳しく書いたほうがよいのではないかと思いました。