GEP-3


元数据
编号

GEP-3

标题

基于命令表达式的 DSL

版本

2

类型

特性

目标

Groovy 1.8 或 2.0

状态

最终

评论

包含在 Groovy 中,并得到进一步增强

负责人

Jochen "blackdrag" Theodorou

创建日期

2009-06-30

最后修改时间 

2018-10-12

摘要

自 Groovy 1.0 起,Groovy 支持命令表达式。这些是无需对参数加括号的函数调用。理论上,这将是 DSL 的良好基础,但我们的命令表达式过于有限,因为我们无法找到处理多个参数的简单规则。本提案现在尝试通过定义这些参数的评估顺序和含义来弥合差距。这个概念非常接近 Scala 允许的,但由于历史原因,并不完全相同。

基本原理

当前的命令表达式

以下是当前命令表达式的示例:

命令表达式 含义

foo a1

foo (a1)

foo {c}

foo ({c})

foo m {c}

foo (m({c})

foo a1, a2

foo (a1, a2)

foo k1:v1, a2, k2:v2

foo ([k1:v1, k2:v2], a2)

foo k1:m{c}

foo ([k1:m({c})])

以下是一些当前不允许的命令表达式示例

命令表达式 可能的含义

foo a1 a2

foo(a1,a2)
foo(a1(a2))
foo(a1).a2

foo a1 a2 a3

foo(a1,a2,a3)
foo(a1(a2(a3)))
foo(a1).a2(a3)
foo(a1,a2(a3))

此列表并非旨在完整。

约束

  • 必须尽可能保持现有的有效用法(出于明显的向后兼容性原因)

  • 评估必须易于解释

  • 语法应支持它

细节

我想允许的表达式如下:

表达式 可能的含义 在旧语法中允许

foo {c}

foo({c})

[thumbs up] (相同含义)

foo a1

foo(a1)

[thumbs up] (相同含义)

foo a1()

foo(a1())

[thumbs up] (相同含义)

foo a1 {c}

foo(a1({c}))

[thumbs up] (相同含义)

foo a1 a2

[thumbs down]

[thumbs down]

foo a1() a2

[thumbs down]

[thumbs down]

foo a1 a2()

[thumbs down]

[thumbs down]

foo a1 a2 {c}

[thumbs down]

[thumbs down]

foo a1 {c} a2

[thumbs down]

[thumbs down]

foo a1 {c} a2 {c}

[thumbs down]

[thumbs down]

foo a1 a2 a3

foo(a1).a2(a3)

[thumbs down]

foo a1() a2 a3()

foo(a1()).a2(a3())

[thumbs down]

foo a1 a2() a3

[thumbs down]

[thumbs down]

foo a1 a2 a3 {c}

foo(a1).a2(a3({c}))

[thumbs down]

foo a1 a2 a3 a4

[thumbs down]

[thumbs down]

foo a1 a2 a3 a4 {c}

[thumbs down]

[thumbs down]

foo a1 a2 a3 a4 a5

foo(a1).a2(a3).a4(a5)

[thumbs down]

foo a1() a2 a3() a4 a5()

foo(a1()).a2(a3()).a4(a5())

[thumbs down]

foo a1 a2 a3 a4 a5 {c}

foo(a1).a2(a3).a4(a5({c})

[thumbs down]

该表足以识别模式。附加的块具有特殊作用,因为它本身不直接作为参数计算。相反,该块始终绑定到前面的标识符并进行函数调用。它本身不是命令表达式,而是一个普通的函数调用表达式。如您所见,此语法很好地扩展了现有的 Groovy 语法。当然,这也意味着在使用多个参数时无法省略逗号。无论如何,对于 DSL 来说这不是问题。

模式总结

  • 命令表达式由偶数个元素组成

  • 这些元素交替出现一个函数名称及其参数(可以是具名参数和非具名参数)

  • 参数元素可以是任何类型的表达式(即函数调用 foo()、foo{} 或一些表达式,例如 x+y)

  • 所有这些函数名称和参数对实际上都是链式函数调用(例如,向“Guillaume”发送“hello”是两个一个接一个的链式函数调用,如 send("hello").to("Guillaume"))

增强命令表达式的有趣好处

我们越来越多的看到 Java Fluent API 链式函数调用,返回 this,以“构建”一个新对象。例如,您可以想象一个用于构建电子邮件消息的 Fluent API,在 Java 中看起来像这样

Email.from("[email protected]").to("[email protected]").subject("hello").body("how are you?")

在 Groovy 中,使用扩展的命令表达式,这可以变成

Email.from "[email protected]" to "[email protected]" subject "hello" body "how are you?"

注意括号和点不见了。

示例:SQL 的 DSL

SELECT "column_name"
FROM "table_name"
WHERE "column_name" IN ('value1', 'value2', ...)

在当前的 Groovy 中,这可以通过以下方式表达:

sql.select(
  "column_name",
  from:"table_name",
  where:"column_name",
  in:['value1','value2',...])

使用这种新的命令 DSL,您也可以这样做

sql.
  select "column_name" \\
  from "table_name" \\
  where "column_name" \\
  in ['value1','value2',...]

应该注意,这两种情况的语义完全不同。在第二种情况下,编写者节省了很多逗号,但当然不是全部。此外,由于缺少任何类型的运算符,例如逗号,使得 DSL 难以跨越多行。一个更扩展的例子是

SELECT COUNT("column_name")
FROM "table_name"
sql.select count("column_name") from "table_name"

用映射样式表达这一点有点困难,因为要将 count 放置在何处……也许一个可能的版本是

sql.select(sql.count("column_name"), from:"table_name"

更多示例想法

以下是一些与不同领域相关的其他示例,这些示例可能使我们更直观地理解这个想法。这些示例还混合了具名参数和非具名参数,以及是否使用闭包。在示例旁的注释中,您将看到等效的非命令表达式解释。

sell 100.shares of MSFT // sell(100.shares).of(MSFT)
every 10.minutes, execute {} // already possible with current command expressions
schedule executionOf { ... } every 10.minutes // scheduler(executionOf({})).every(10.minutes)
blend red, green of acrylic // blend(red, gree).of(acrylic)

// named parameters into the mix
select from: users where age > 32 and sex == 'male'
// equivalent to select(from: users).where(age > 32).and(sex == 'male')
// not that however for this example, it would be intersting
// to transparently convert the boolean conditions into closure expressions!

// a recipe DSL
take mediumBowl
combine soySauce, vinegar, chiliPowder, garlic
place chicken in sauce
turn once to coat
marinate 30.minutes at roomTemperature

在赋值情况下对命令表达式的扩展

目前,命令表达式允许作为独立的顶层语句或表达式,但您无法将此类表达式分配给变量,同时保持该优美的 DSL 语法。例如,虽然您可以这样做

move left

如果您想分配该命令(它可以返回一个 Position 实例),您想这样做

def newPosition = move left

但是您仍然必须这样做

def newPosition = move(left)

因此,GEP-3 提案还建议我们将命令表达式扩展为允许在赋值的 RHS 上使用。

与 Scala 的区别

由于历史原因

println foo

必须得到支持。在 Scala 中,这似乎不是一个有效的版本,因为它将被解释为

println.foo

而不是

this.println foo

另一方面

foo bar a1

在 Scala 中被解释为

foo.bar(a1)

在当前的 Groovy 中以及本提案之后,它也是无效的。因此可以说,本提案不如 Scala 面向对象,因为 DSL 通常从函数开始,而不是从对象开始。另一方面,可以编写

foo.bar a1

因此,Groovy 表示法会稍微冗长一些,但不会太多。

待评估:带显式括号的混合案例

另一个可能的受支持案例是混合使用带显式括号的函数调用以及扩展的命令表达式。这样做的好处是,还可以调用不带参数的函数,并且允许奇数个“元素”(即一个函数名称或一个参数)。

m1 a m2 b m3()
m1 a m2() m3 b
m1() m2 a m3 b

将分别等效于

m1(a).m2(b).m3()
m1(a).m2().m3(b)
m1().m2(a).m3(b)

请注意,带显式括号的函数调用也可以接受多个参数。例如,这也是一个有效的混合命令表达式

m1 a m2(1, 2, 3) m3 b

JIRA 问题

  • 实现 GEP-3:扩展的命令表达式 GROOVY-4384

  • 能够在 RHS 上使用(扩展的)命令表达式 GROOVY-4401

  • 允许链式调用中的零参数函数 GROOVY-4402

  • 消除在扩展命令表达式的参数中使用减号、方括号或花括号时的歧义 GROOVY-4403

更新历史

1 (2009-06-17)

从 Codehaus wiki 中提取的版本

2 (2018-10-11)

无数次微调