LoginSignup
0
0

【jq】関数呼び出しの引数は遅延評価

Posted at

TL; DR

sample.jq
def in_a(pathexps):
  .a | pathexps;

in_a(.b)
$ echo '{"a": {"b": 1}}' | jq -f sample.jq
1

はじめに

9/7に、jqの新バージョンが5年ぶりにリリースされました :tada:

便利な関数もたくさん追加されており、例えば pick では指定したパスの要素のみを抜き出せます。

$ jq -n '{"a": 1, "b": {"c": 2, "d": 3}, "e": 4} | pick(.a, .b.c, .x)'
{
  "a": 1,
  "b": {
    "c": 2
  },
  "x": null
}

ところで、JS等 正格評価の言語では上記の関数は実装できません
正格評価の場合 pick(.a, .b.c, .x)pick(1, 2, null) に評価されてから関数呼び出しされるため、関数側ではパスが何であったか知ることができません。

そこで本記事では、jqの引数の評価について調べてみました。

関数に渡されるのはパスの式

結論としては、jqの引数は遅延評価されていました1

以下のように、存在しないパスを指定してもエラーは発生しません。ということは、関数呼び出しの時点ではパスの式は値に評価されていません。

sample.jq
# 引数のpathexpsは捨てる
def capture(pathexps):
  100;

capture(.a.b.c)
$ echo '{"a": {"b": 1}}' | jq -f sample.jq
100

また、引数は ただのパスの式 なので、パイプの後に持ってくることも可能です。

sample.jq
$ cat sample.jq
def in_a(pathexps):
  .a | pathexps;

# .a | .b と同じ
in_a(.b)
$ echo '{"a": {"b": 1}}' | jq -f sample.jq
1

ただし、ずっとパスとして扱えるわけではなく、関数本体の式で使用すると値に評価されます。

sample.jq
def idx(pathexps):
  # ここでパスの値に評価される
  pathexps;

idx(.a.b)
$ echo '{"a": {"b": 1}}' | jq -f sample.jq
1

引数を「パスを表す値」として持ちまわりたい場合は path を使用します。

# パスの式を配列に変換(配列は第一級なのでそのまま変数等に代入可能)
$ jq -n 'path(.a.b[1])'
[
  "a",
  "b",
  1
]
# 再びパスとして使用する場合は getpath
$ echo '{"a": {"b": 1}}' | jq 'getpath(path(.a.b))'
1
# もちろん直接配列を使用してもよい
$ echo '{"a": {"b": 1}}' | jq 'getpath(["a", "b"])'
1

pickの実装

冒頭のpickの実装は以下のようになっています。

src/builtin.jq
def pick(pathexps):
  . as $in
  | reduce path(pathexps) as $a (null;
      setpath($a; $in|getpath($a)) );

reduceを使って、パスの場所にその値を格納しています。パスに指定した場所だけ格納されるため、結果指定したパスのみが抜き出されるという仕組みです。

おわりに

以上、jqの引数の評価についての紹介でした。jqは名前的にJavaScriptに似た特徴を持つと思い込んでいたので、この挙動には驚きました。

色々応用が効きそうなので、ぜひjq芸に使っていきましょう! (保守性は見なかったことにする)

  1. 処理系まで目を通していないので実際はもう少し複雑かもしれません(VMのC実装を追えなかった... )。ただし、pick の引数名が pathexps であることからも意図した遅延評価であると思われます。

0
0
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
0
0