[JSONata] ドット演算子について理解を深める
こんにちは。サービス開発室の武田です。
引き続き、AWS Step FunctionsのためにJSONataの勉強中です。今日は、Step Functions自体は出てきません。
今回はドット演算子(.
)についてです。おそらく普段からAWSを使用している方は、jqやJMESPathに馴染みのある方が多いでしょう。そういう人は「.
?あぁプロパティ掘り下げるやつね」とさっと理解すると思います。私もそうでした。
しかしドキュメントを読んでみると、なんだかそんな単純なものではないようです。具体例を見ながら、このドット演算子(.
)について理解を深めていきましょう。
. は Map なんだ
Mapといっても「地図」ではなくて「写像」の方です。関数型プログラミングとかでよく出てくるアレです。
次の式を例に、どういうことなのか見ていきましょう。
$map($range(1, 10, 1), function($a) { $a * $a })
これは、「「1から10までの値を持った配列」を生成し、各要素を二乗した配列を生成する」という式です。map
やrange
といったプログラミング言語でもお馴染みの関数がJSONataでも組込みで実装されています。JSONataではラムダ式(匿名関数)がサポートされているため、$map
の第2引数に渡しています。なおJSONataの関数はファーストクラスオブジェクトというだけでなく、部分適用関数などもサポートされており、とてもパワフルです。これもまた機会があれば取り上げていきます。
先ほどの式はきちんと意図したとおりに動きますが冗長です。少しずつリファクタリングしていきましょう。まずは$range
を書き換えます。JSONataではRangeオペレーター(..
)がサポートされており、今回はこれが使えます。次のように書き換えられます。
$map([1..10], function($a) { $a * $a })
続いて$map
を書き換えます。ここが 今回の趣旨 と言ってもいいでしょう。そう、実は$map
を使わなくても、各要素に関数を適用できるのです。書き換えると次のようになります。
[1..10].(function($a) { $a * $a }($))
私は最初なんでこれが動くのかよくわかりませんでした。しかし「.
は Map」ということが理解できると腑に落ちます。JSONataのドット演算子(.
)はまさしく「演算子」なんです。左オペランド([1..10]
)に対して右オペランド((function($a) { $a * $a }($))
)を Mapする という演算子。それがドット演算子(.
)です。
右オペランド内にある$
は、コンテキストにアクセスするためのシンボルで、今回で言えば配列の各要素が渡されます。
さてこれが理解できたらこのエントリの目標は達成なのですが、もう少し進めましょう。先ほど$
を使えば要素にアクセスできることがわかったので、二乗する処理は単に次のように書き換えられます。
[1..10].($ * $)
どうです?すごくないですか?ちなみに冪乗関数は$power
が組込みで提供されているので、同じ処理は次のようにも書けます。
[1..10].$power(2)
応用として、「ランダムな値を10個持った配列」も簡単に作れます。
[1..10].$random()
普段のプロパティアクセスはどう捉えればいいか
ドット演算子(.
)がMapだということは理解できてきたでしょうか。それでは、最初に挙げた「プロパティアクセス」の挙動はどう理解すればいいでしょう。実はこれも写像のひとつとしてみなせるため、特別扱いなどは必要ありません。
まずは次の例を考えてみましょう。
(
$o := [{ "a": 1 }, { "a": 2 }, { "b": 2 }];
$o.a /* [1, 2] */
)
$o
は3つの要素を持った配列です。$o.a
は各要素の「aプロパティだけを返す写像」とみなせます。そうすると、[1, 2, nothing]
ということになりますが、JSONataでは nothingは何もない 意味ですので、上記の式は結果として[1, 2]
を返します。
同様に次の例も考えてみます。
(
$o := { "a": 1 };
$o.a /* 1 */
)
$o
が配列ではなく単一のオブジェクトになりました。JSONataでは、「単一の値は、1要素の配列として扱う」ことで、この挙動をスマートに解決します。
つまり内部では、次のような処理が行われていると考えてください。
{ "a": 1 }
→(1要素の配列とみなす) [{ "a": 1 }]
→(Map処理を解決) [1]
→(1要素なので単一の値とする) 1
というわけです。
また補足ですが、ドット演算子(.
)は左結合です。そのためa.b.c
は(a.b).c
となるため、違和感なく使用できるわけですね。
まとめ
JSONataのドット演算子(.
)はMapなんだ!というお話でした。