= GEP-3:基于命令表达式的 DSL :icons: font .Metadata **** [horizontal,options="compact"] *编号*:: 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 允许的非常接近,但由于历史原因并不完全相同。 == 基本原理 === 当前命令表达式 当前命令表达式的示例有: [options="header"] |=== | 命令表达式 | 含义 m| foo a1 m| foo (a1) m| foo {c} m| foo ({c}) m| foo m {c} m| foo (m({c}) m| foo a1, a2 m| foo (a1, a2) m| foo k1:v1, a2, k2:v2 m| foo ([k1:v1, k2:v2], a2) m| foo k1:m{c} m| foo ([k1:m({c})]) |=== 当前命令表达式中不允许的示例: [options="header"] |=== | 命令表达式 | 可能的含义 m| foo a1 a2 a| ---- foo(a1,a2) foo(a1(a2)) foo(a1).a2 ---- m| foo a1 a2 a3 a| ---- foo(a1,a2,a3) foo(a1(a2(a3))) foo(a1).a2(a3) foo(a1,a2(a3)) ---- |=== 此列表并非旨在完整。 === 约束 * 现有有效用法必须尽可能保留(出于明显的向后兼容性原因) * 评估必须易于解释 * 语法应该支持它 === 详细信息 我想允许的表达式例如: [options="header"] |=== | 表达式 | 可能的含义 | 旧语法中是否允许 m| foo {c} m| foo({c}) | icon:thumbs-up[] (相同含义) m| foo a1 m| foo(a1) | icon:thumbs-up[] (相同含义) m| foo a1() m| foo(a1()) | icon:thumbs-up[] (相同含义) m| foo a1 {c} m| foo(a1({c})) | icon:thumbs-up[] (相同含义) m| foo a1 a2 | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1() a2 | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2() | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2 {c} | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 {c} a2 | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 {c} a2 {c} | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2 a3 m| foo(a1).a2(a3) | icon:thumbs-down[] m| foo a1() a2 a3() m| foo(a1()).a2(a3()) | icon:thumbs-down[] m| foo a1 a2() a3 | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2 a3 {c} m| foo(a1).a2(a3({c})) | icon:thumbs-down[] m| foo a1 a2 a3 a4 | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2 a3 a4 {c} | icon:thumbs-down[] | icon:thumbs-down[] m| foo a1 a2 a3 a4 a5 m| foo(a1).a2(a3).a4(a5) | icon:thumbs-down[] m| foo a1() a2 a3() a4 a5() m| foo(a1()).a2(a3()).a4(a5()) | icon:thumbs-down[] m| foo a1 a2 a3 a4 a5 {c} m| foo(a1).a2(a3).a4(a5({c}) | icon:thumbs-down[] |=== 该表足以识别模式。附加的块具有特殊作用,因为它本身不直接算作参数。相反,该块始终绑定到前面的标识符并进行方法调用。这本身不是命令表达式,而是普通方法调用表达式。正如所见,此语法很好地扩展了现有 Groovy 语法。当然,这也意味着,如果使用多个参数,将无法省略逗号。这种情况目前也不支持。然而,对于 DSL 来说,这并不是一个真正的问题。 ==== 模式总结 * 命令表达式由偶数个元素组成 * 元素交替为方法名及其参数(可以是命名参数和非命名参数) * 参数元素可以是任何类型的表达式(即方法调用 foo()、foo{} 或像 x+y 这样的表达式) * 所有这些方法名和参数对实际上都是链式方法调用(即 send "hello" to "Guillaume" 是两个方法一个接一个地链式调用,如 send("hello").to("Guillaume")) ==== 增强命令表达式的有趣好处 我们越来越多地看到 Java 流式 API 链式方法调用,返回 this,以“构建”一个新对象。例如,你可以想象一个用于构建电子邮件消息的流式 API,它在 Java 中看起来像这样: ``` Email.from("foo@example.com").to("bar@example.com").subject("hello").body("how are you?") ``` 在 Groovy 中,使用扩展命令表达式,这可以变成: ``` Email.from "foo@example.com" to "bar@example.com" 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 {} // 使用当前命令表达式已经可能 schedule executionOf { ... } every 10.minutes // scheduler(executionOf({})).every(10.minutes) blend red, green of acrylic // blend(red, gree).of(acrylic) // 命名参数混入其中 select from: users where age > 32 and sex == 'male' // 等效于 select(from: users).where(age > 32).and(sex == 'male') // 注意,对于这个例子,将布尔条件透明地转换为闭包表达式会很有趣! // 一个食谱 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:扩展命令表达式 https://issues.apache.org/jira/browse/GROOVY-4384[GROOVY-4384] * 能够在 RHS 上使用(扩展)命令表达式 https://issues.apache.org/jira/browse/GROOVY-4401[GROOVY-4401] * 允许在调用链中使用零参数方法 https://issues.apache.org/jira/browse/GROOVY-4402[GROOVY-4402] * 消除在扩展命令表达式中将减号、[] 或 {} 用作参数的情况歧义 https://issues.apache.org/jira/browse/GROOVY-4403[GROOVY-4403] == 更新历史 1 (2009-06-17):: 从 Codehaus wiki 提取的版本 2 (2018-10-11):: 众多小幅调整

GEP-3