【发布时间】:2018-11-12 03:40:21
【问题描述】:
概述
我有一个基于 Kotlin 的项目,它定义了一个 DSL,但由于下面给出的原因,我现在正在研究用 Scala 编写我的项目是否更好。由于 Scala 似乎不适合使用 as much ease as in Kotlin 创建 DSL,我不完全确定如何在 Scala 中重新创建相同的 DSL。
在它被标记为 this question 的重复之前,我已经看过了,但我的 DSL 要求有些不同,我无法从中找出解决方案。
详情
我正在尝试创建一个基于流程的编程系统来开发自动车辆部件测试程序,在过去的几周里,我一直在 Kotlin 中测试它的实现,因为它似乎支持很多非常适合创建 FBP 系统的功能(原生协程支持、使用类型安全构建器轻松创建 DSL 等)。
尽管 Kotlin 非常棒,但我开始意识到,如果 FBP 的实现语言更实用,它会很有帮助,因为 FBP 似乎与函数式语言有很多共同点。特别是,能够定义和使用类型类对于这样的项目非常有用。
在 Kotlin 中,我创建了一个 DSL,它表示基于流的系统中节点之间的“胶水”语言。例如,假设存在两个黑盒进程 Add 和 Square,我可以定义一个“复合”节点,将两个数字的总和平方:
@CompositeNode
private fun CompositeOutputtingScalar<Int>.addAndSquare(x: Int, y: Int) {
val add = create<Add>()
val square = create<Square>()
connect {
input(x) to add.x
input(y) to add.y
add.output to square.input
square.output to output
}
}
这个想法是 connect 是一个采用 ConnectionContext.() -> Unit 形式的 lambda 的函数,其中 ConnectionContext 定义了中缀函数 to 的各种重载(遮蔽了 Kotlin 中的内置 to 函数stdlib) 允许我定义这些进程(或节点)之间的连接。
这是我尝试在 Scala 中做类似的事情:
class OutputPort[-A] {
def connectTo(inputPort: InputPort[A]) {}
}
class InputPort[+A]
object connect {
val connections = new ListBuffer[Connection[_]]()
case class Connection[A](outputPort: OutputPort[A], inputPort: InputPort[A])
class ConnectionTracker() {
def track[A](connection: Connection[A]) {}
}
// Cannot make `OutputPort.connectTo` directly return a `Connection[A]`
// without sacrificing covariance, so make an implicit wrapper class
// that does this instead
implicit class ExtendedPort[A](outputPort: OutputPort[A]) {
def |>(inputPort: InputPort[A]): Unit = {
outputPort connectTo inputPort
connections += Connection(outputPort, inputPort)
}
}
}
def someCompositeFunction() {
val output = new OutputPort[Int]
val input = new InputPort[Int]
output |> input // Should not be valid here
connect {
output |> input // Should be valid here
}
}
现在这不会编译,因为ConnectablePort 不在范围内。我可以通过以下方式将其纳入范围:
import connect._
connect {
output |> input // Should be valid here
}
但是,必须在节点定义中执行此操作是不可取的。
总而言之,我如何在 Scala 中重新创建我在 Kotlin 中制作的 DSL?作为参考,这是我定义 Kotlin DSL 的方式:
interface Composite {
fun <U : ExecutableNode> create(id: String? = null): U
fun connect(apply: ConnectionContext.() -> Unit)
class ConnectionContext {
val constants = mutableListOf<Constant<*>>()
fun <T> input(parameter: T): OutputPort<T> = error("Should not actually be invoked after annotation processing")
fun <T> input(parameterPort: OutputPort<T>) = parameterPort
fun <T> constant(value: T) = Constant(value.toString(), value)
infix fun <U, V> U.to(input: InputPort<V>): Nothing = error("Cannot connect value to specified input")
infix fun <U> OutputPort<U>.to(input: InputPort<U>) = this join input
infix fun <T, U> T.to(other: U): Nothing = error("Invalid connection")
}
}
interface CompositeOutputtingScalar<T> : Composite {
val output: InputPort<T>
}
interface CompositeOutputtingCluster<T : Cluster> : Composite {
fun <TProperty> output(output: T.() -> TProperty): InputPort<TProperty>
}
【问题讨论】:
-
Scala 3 很可能具有启用“类型安全构建器”的功能:dotty.epfl.ch/docs/reference/implicit-function-types.html
-
@Jasper-M 这太棒了。可惜 Scala 3 最早要到 2020 年才会发布。