Обзор
У меня есть проект на основе Kotlin, который определяет DSL, но по причинам, указанным ниже, я сейчас выясняю, лучше ли было бы написать мой проект в Scala. Поскольку Scala, похоже, не позволяет создавать DSL с с такой же легкостью, как в Kotlin , я не совсем уверен, как воссоздать тот же DSL в Scala.
До того, как это будет помечено как дубликат этого вопроса , я посмотрел на него, но мои требования к DSL несколько отличаются, и я не смог найти решение из этого.
Подробнее
Я пытаюсь создать систему основанного на потоке программирования для разработки автоматизированных процедур проверки деталей транспортных средств, и в течение последних нескольких недель я тестировал реализацию этого в Котлине, поскольку, похоже, он много поддерживает функций, которые действительно хороши для создания систем 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
, где ConnectionContext
определяет различные перегрузки инфиксной функции to
(скрывая встроенную функцию to
в Kotlin 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
}
Однако нежелательно делать это в пределах определения узла.
Подводя итог, как я могу воссоздать DSL, который я сделал в Kotlin в Scala? Для справки, вот как я определил свой DSL Kotlin:
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>
}