表达式类型

JEP

8

作者

詹姆斯·萨耶维尼

状态

认可的

创建

2013年3月2日

摘要

这个JEP建议对JMESPath进行语法修改,以允许函数中的表达式引用。这样可以实现如下功能 sort_bymax_bymin_by . 这些函数接受解析为表达式类型的参数。这将启用一些功能,例如根据对每个数组元素求值的表达式对数组进行排序。

动机

在其他表达式语言中很常见的一个有用的特性是能够根据特定的键对JSON对象进行排序。例如,给定一个JSON对象:

{
  "people": [
       {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
       {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
       {"age": 30, "age_str": "30", "bool": true, "name": "c"},
       {"age": 50, "age_str": "50", "bool": false, "name": "d"},
       {"age": 10, "age_str": "10", "bool": true, "name": 3}
  ]
}

当前无法对 people 按数组 age 钥匙。也, sort 没有为 object 类型,所以目前无法对 people 数组。为了对 people 数组,我们需要知道在对数组排序时使用什么键。

这种基于密钥的排序概念可以推广。不需要键名,可以提供一个表达式来计算每个元素。在最简单的情况下,这个表达式就是 identifier ,但可以使用更复杂的表达式,例如 foo.bar.baz .

实现这一点的一个简单方法可能是创建这样一个函数:

sort_by(array arg1, expression)

# Called like:

sort_by(people, age)
sort_by(people, to_number(age_str))

但是,有一个问题 sort_by 功能如上所述。如果我们遵循函数参数解析过程,我们得到:

sort_by(people, age)

# 1. resolve people
arg1 = search(people, <input data>) -> [{"age": ...}, {...}]

# 2. resolve age
arg2 = search(age, <input data>) -> null

sort_by([{"age": ...}, {...}], null)

第二个参数根据当前节点和表达式进行计算 age 将决心 null 因为输入数据没有 age 钥匙。需要有某种方法来指定表达式应计算为表达式类型:

arg = search(<some expression>, <input data>) -> <expression: age>

那么函数的定义 sort_by 可能是:

sort_by(array arg1, expression arg2)

规范

以下语法规则将更新为::

function-arg        = expression /
                      current-node /
                      "&" expression

计算表达式引用时应返回“expression”类型的对象。函数支持的数据类型列表现在将是:

  • 数字(JSON中的整数和双精度浮点格式)

  • 一串

  • 布尔 (truefalse

  • 数组(一个有序的值序列)

  • 对象(键值对的无序集合)

  • 无效的

  • 表达式(用 &expression

现在可以使用这个新的 expression 类型。另外,函数签名可以指定表达式的返回类型。类似地,数组如何使用 array[type] 语法,表达式可以使用 expression->type 语法。

请注意,任何有效的表达式都允许在 & ,因此以下表达式是有效的:

sort_by(people, &foo.bar.baz)
sort_by(people, &foo.bar[0].baz)
sort_by(people, &to_number(foo[0].bar))

附加功能

将添加以下功能:

sort_by

sort_by(array elements, expression->number|expression->string expr)

使用表达式对数组排序 expr 作为排序键。下面是几个使用 people 数组(上面定义的)作为给定的输入。 sort_by 遵循与 sort 功能。

实例

表情

结果

sort_by(people, &age)[].age

[10,20,30,40,50]

sort_by(people, &age)[0]

{“age”:10,“age_str”:“10”,“bool”:真,“name”:3}

sort_by(people, &to_number(age_str))[0]

{“age”:10,“age_str”:“10”,“bool”:真,“name”:3}

max_by

max_by(array elements, expression->number expr)

使用表达式返回数组中的最大元素 expr 作为比较键。返回整个maximum元素。下面是几个使用 people 数组(上面定义的)作为给定的输入。

实例

表情

结果

max_by(people, &age)

{“age”:50,“age_str”:“50”,“bool”:假,“name”:“d”},

max_by(people, &age).age

50

max_by(people, &to_number(age_str))

{“age”:50,“age_str”:“50”,“bool”:假,“name”:“d”},

max_by(people, &age_str)

<错误:无效类型>

max_by(people, age)

<错误:无效类型>

min_by

min_by(array elements, expression->number expr)

使用表达式返回数组中的最小元素 expr 作为比较键。返回整个maximum元素。下面是几个使用 people 数组(上面定义的)作为给定的输入。

实例

表情

结果

min_by(people, &age)

{“age”:10,“age_str”:“10”,“bool”:真,“name”:3}

min_by(people, &age).age

10

min_by(people, &to_number(age_str))

{“age”:10,“age_str”:“10”,“bool”:真,“name”:3}

min_by(people, &age_str)

<错误:无效类型>

min_by(people, age)

<错误:无效类型>

选择

审议了若干备选提案。下面概述了其中的几个备选方案。

论元分解器中的逻辑

第一个建议的选择(最初是在JEP-3中,但后来被删除了)是不使用任何语法结构来指定函数,并允许函数签名指示是否解决了一个参数。的签名 sort_by 可能是:

sort_by(array arg1, any arg2)
arg1 -> resolved
arg2 -> not resolved

然后,参数解析器将反省函数的参数规范,以确定要执行的操作。粗略地说,伪代码应该是:

call-function(current-data)
arglist = []
for each argspec in functions-argspec:
    if argspect.should_resolve:
      arglist <- resolve(argument, current-data)
    else
      arglist <- argument
type-check(arglist)
return invoke-function(arglist)

但是,有几个原因不这样做:

  • 这是一个特定的实现。这种实现在字节码虚拟机中是具有挑战性的,因为调用字节码通常会将参数解析到堆栈上,并允许函数从堆栈中弹出参数并执行自己的arity验证。

  • 这偏离了传统上如何实现功能的“标准”模型。

将表达式指定为字符串

另一个提议的替代方案是允许表达式为字符串类型,并赋予函数解析/求值表达式的能力。这个 sort_by 函数如下所示:

sort_by(people, `age`)
sort_by(people, `foo.bar.baz`)

没有选择这项提案的主要原因是:

  • 这使实现复杂化。对于执行AST内联的实现,这意味着AST节点需要访问解析器。对于外部树访问者,访问者需要访问解析器。

  • 移动什么 能够 由编译时错误变成运行时错误。表达式字符串的计算在调用函数时发生。