Scala DSL:模仿英语的调用
Scala DSL: invocation that mimics English
我是 scala 的新手,这更像是一个好奇的问题。
假设我有一个 class
class Container()
{
def add(item: Item) ...
}
我可以这样调用它:container add item
。
我想知道如何将此调用变成类似英语的 add item to container
?它可能会违反风格指南,但正如我所说,我只是好奇。
我觉得不可能。
这些类型的 Scala DSL 本质上只是没有点和括号的方法调用链:
v1.m1(v2).m2(v3)
写成
v1 m1 v2 m2 v3
具有一些值 v1, v2, ...
和方法 m1, m2, ...
。
给你
'add' value 'to' value
无论您如何定义 'add'
和 'to'
,它都不适合 value-method-value-method
模式。
Andrey 指出这是不可能的,因为方法和值的顺序。但是你可以通过礼貌地添加 "please".
来解决这个问题
please add item to container
这里是 please
...
的实现
object please {
case class Adder(item: Item) {
def to(c: Container) =
c.add(item)
}
def add(i: Item) = Adder(i)
}
如果您不喜欢 please
,那么其他选项包括 siri
、alexa
、heyGoogle
和 hal
:)
因为我喜欢一个很好的挑战,所以我尝试了如果你滥用 Scala 必须提供的几乎所有危险 and/or 实验性功能,你是否无法到达你想要的地方,结果证明这是可能的:
scala> :pa
// Entering paste mode (ctrl-D to finish)
import scala.language.dynamics
import scala.language.postfixOps
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
case class Item(i: Int)
class Container { var item: Item = null; def add(i: Item) = item = i }
object to
object add extends Dynamic { def applyDynamic(item: String)(a: to.type) = new AddContainer}
class AddContainer extends Dynamic { def selectDynamic(container: String): Unit = macro addMacro }
/**
* Don't try this at home: not a foolproof macro!
*/
def addMacro(c: Context)(container: c.Tree): c.Tree = {
import c.universe._
val Literal(Constant(containerName: String)) = container
val q"$_.applyDynamic($item)($_)" = c.prefix.tree
val Literal(Constant(itemName: String)) = item
q"${TermName(containerName)}.add(${TermName(itemName)})"
}
// Exiting paste mode, now interpreting.
scala> object Test {
| val item = Item(1)
| val container = new Container
| println(container.item)
|
| add item to container
|
| println(container.item)
| }
defined object Test
scala> Test
null
Item(1)
让我们把它分解成可以理解的部分。
您想编写 add item to container
,它被解析为 add.item(to).container
。实施的后果是:
- 您需要启用 postfixOps 才能将
container
用作后缀运算符。
item
和 container
都在方法调用位置,但我们希望它们引用当前范围内的变量。为了克服这个问题,我们需要结合两个特性:
- 用
Dynamic
我们可以把a.b(c)
变成a.applyDynamic("b")(c)
,把a.b
变成a.selectDynamic("b")
。所以方法名称变成了我们可以动态处理的字符串。
- 然后我们需要一个宏来将这些字符串转换为引用我们变量的标识符。我们从语法树中提取
String
文字,将它们转换为 TermName
并输出一个看起来像 container.add(item)
. 的新语法树
为简单起见,此宏仅适用于最微不足道的情况。要使它可靠地工作可能需要付出更多的努力。并且说得很清楚:这只是为了回答 "what will it take" 的问题。我怀疑是否有一个用例可以证明实际这样做是合理的。然而,此处使用的技术可能适用于一些非常高级但有效的用例。
我是 scala 的新手,这更像是一个好奇的问题。
假设我有一个 class
class Container()
{
def add(item: Item) ...
}
我可以这样调用它:container add item
。
我想知道如何将此调用变成类似英语的 add item to container
?它可能会违反风格指南,但正如我所说,我只是好奇。
我觉得不可能。
这些类型的 Scala DSL 本质上只是没有点和括号的方法调用链:
v1.m1(v2).m2(v3)
写成
v1 m1 v2 m2 v3
具有一些值 v1, v2, ...
和方法 m1, m2, ...
。
给你
'add' value 'to' value
无论您如何定义 'add'
和 'to'
,它都不适合 value-method-value-method
模式。
Andrey 指出这是不可能的,因为方法和值的顺序。但是你可以通过礼貌地添加 "please".
来解决这个问题please add item to container
这里是 please
...
object please {
case class Adder(item: Item) {
def to(c: Container) =
c.add(item)
}
def add(i: Item) = Adder(i)
}
如果您不喜欢 please
,那么其他选项包括 siri
、alexa
、heyGoogle
和 hal
:)
因为我喜欢一个很好的挑战,所以我尝试了如果你滥用 Scala 必须提供的几乎所有危险 and/or 实验性功能,你是否无法到达你想要的地方,结果证明这是可能的:
scala> :pa
// Entering paste mode (ctrl-D to finish)
import scala.language.dynamics
import scala.language.postfixOps
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
case class Item(i: Int)
class Container { var item: Item = null; def add(i: Item) = item = i }
object to
object add extends Dynamic { def applyDynamic(item: String)(a: to.type) = new AddContainer}
class AddContainer extends Dynamic { def selectDynamic(container: String): Unit = macro addMacro }
/**
* Don't try this at home: not a foolproof macro!
*/
def addMacro(c: Context)(container: c.Tree): c.Tree = {
import c.universe._
val Literal(Constant(containerName: String)) = container
val q"$_.applyDynamic($item)($_)" = c.prefix.tree
val Literal(Constant(itemName: String)) = item
q"${TermName(containerName)}.add(${TermName(itemName)})"
}
// Exiting paste mode, now interpreting.
scala> object Test {
| val item = Item(1)
| val container = new Container
| println(container.item)
|
| add item to container
|
| println(container.item)
| }
defined object Test
scala> Test
null
Item(1)
让我们把它分解成可以理解的部分。
您想编写 add item to container
,它被解析为 add.item(to).container
。实施的后果是:
- 您需要启用 postfixOps 才能将
container
用作后缀运算符。 item
和container
都在方法调用位置,但我们希望它们引用当前范围内的变量。为了克服这个问题,我们需要结合两个特性:- 用
Dynamic
我们可以把a.b(c)
变成a.applyDynamic("b")(c)
,把a.b
变成a.selectDynamic("b")
。所以方法名称变成了我们可以动态处理的字符串。 - 然后我们需要一个宏来将这些字符串转换为引用我们变量的标识符。我们从语法树中提取
String
文字,将它们转换为TermName
并输出一个看起来像container.add(item)
. 的新语法树
- 用
为简单起见,此宏仅适用于最微不足道的情况。要使它可靠地工作可能需要付出更多的努力。并且说得很清楚:这只是为了回答 "what will it take" 的问题。我怀疑是否有一个用例可以证明实际这样做是合理的。然而,此处使用的技术可能适用于一些非常高级但有效的用例。