LoginSignup
7
5

More than 5 years have passed since last update.

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

Posted at

はじめに

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

環境情報

Lodash 4.17.10

省略記法の例

以下はLodashのドキュメントのfilter関数のExampleです。

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, find)もこのような省略記法が使用できます。

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

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

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

Lodash 4 Cookbookの解説

何気なく検索してみると、Lodash4 Cookbook に良さげな説明が書かれていました。

以下、 Lodash 4 - Cookbook Predicates より引用です。

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関数の定義です。

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の定義を覗いてみます。

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

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

baseIteratee

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

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に書かれていたルールに対応していそうな内容です!
引数の valuepredicate が渡されることに注意します。

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

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

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

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

baseMatchesPropertybaseMatches はそれぞれ、 _.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の省略記法については、公式のドキュメントでもう少し詳しく書いたほうがよいのではないかと思いました。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5