#scala #dsl
#scala #dsl
Вопрос:
Я все еще работаю над своим самым масштабируемым функциональным DSL.
Я бы хотел 3 варианта моей given
функции. Все берут tokens: Any
, и тогда либо
A. Блок block: => Unit
, который выполняется позже
given("user visits", the[AdminHomePage]) {
// just code
}
B. Блок block: Any => Unit
, который выполняется позже с токенами
given("user visits", the[AdminHomePage]) {
x: Any => x match {
case ("user visits", pageClass:Class[Page]) =>
startPage(pageClass)
}
}
C. Нет блока, в котором токены обрабатываются другой функцией
given("user visits", the[AdminHomePage])
Теперь, когда я определяю все три метода
def given(tokens: Any) = ...
def given(tokens: Any)(block: Any => Unit) = block(tokens)
def given(tokens: Any)(block: => Unit) = block
Компилятор считает их неоднозначными.
ambiguous reference to overloaded definition, both method given in trait GivenWhenThenFeatureSpec of type (tokens: Any)(block: => Unit)Unit and method given in trait GivenWhenThenFeatureSpec of type (tokens: Any)(block: (Any) => Unit)Unit match argument types
Как можно устранить неоднозначность или написать единый метод, который может различать блок (или его отсутствие)?
Комментарии:
1. Вы имеете в виду написать
tokens: Any*
или я что-то упускаю?2. Ах да, или, скорее, нет. Это так
tokens: Any
, и аргументы упакованы в виде кортежа, с которым я сопоставляю (a-la B выше).3. Итак, окончательная обработка DSL — это метод с совпадением по кортежу и множеством обращений, запускаемый первой (неблочной) формой. Блочные формы существуют для того, чтобы позволить мне создавать прототипы реализаций случаев.
4. В случае (C) токены обрабатываются немедленно или позже?
Ответ №1:
Мне нравится решение @MachAndy, приведенное выше, за исключением импорта преобразований unit2emptyfunction, которые, на мой взгляд, могут мешать или покрывать ошибки другого типа.
Если вместо этого вы определите следующее:
object Given {
trait Processor {
def process(tokens: Any)
}
class ProcessorA(block: =>Unit) extends Processor {
def process(tokens: Any) = {
block // execute or store block for later, ignoring tokens
}
}
class ProcessorB(block: Any=>Unit) extends Processor {
def process(tokens: Any) = {
block(tokens) // or store block for later execution
}
}
class ProcessorC extends Processor {
def process(tokens: Any) = {
// do something defaultish with the tokens
}
}
implicit def blockToProcessorA(block: =>Unit) = new ProcessorA(block)
implicit def blockToProcessorB(block: Any=>Unit) = new ProcessorB(block)
implicit val processorC = new ProcessorC
def given(tokens: Any)(implicit p: Processor) = p.process(tokens)
}
Затем вы можете просто:
import Given._
given("user visits", the[AdminHomePage])
given("user visits", the[AdminHomePage]) {
// some stuff that ignores tokens
}
given("user visits", the[AdminHomePage]) { x: Any =>
x match {
// do something that looks at the tokens
}
}
Комментарии:
1. Пока это работает очень хорошо, с небольшим усложнением, заключающимся в том, что мне пришлось объявить все блоки как
=> Any
, чтобы позволить содержимому блока оценивать что-либо (что отбрасывается). Спасибо.2. О, и еще одно крошечное улучшение заключалось в том, что
implicit object ProcessorC
позволило мне удалить неявный val.3. Это было именно то улучшение, которое я искал! Большое вам спасибо за это решение.
Ответ №2:
У меня есть решение, но я думаю, что его можно улучшить.
Я использовал один given
метод в качестве ввода и неявно предоставлял или нет тело
def given[A](tokens: A)(implicit block: A => Unit) {
block(tokens)
}
Сначала вот сахар, позволяющий использовать Unit
блок в качестве Any => Unit
implicit def unit2emptyfunction(body: Unit): Any => Unit = {
case _ => body
}
Чтобы иметь возможность работать в случае C, я предоставляю тело по умолчанию для заполнения block
параметра, который ничего не делает.
implicit val doNothing: Any => Unit = { }
Теперь вы можете использовать его таким образом :
/*
* A case, block is implicitly converted into a A => Unit
* although it doesn't use the argument
*/
given("user visits", the[AdminHomePage]) {
// just code
}
/*
* B case, block is fully provided and thus not implicitly converted
*/
given("user visits", the[AdminHomePage]) {
case ("user visits", pageClass: Class[Page]) =>
startPage(pageClass)
}
// C case, block implicitly provided by doNothing implicit val
given("user visits", the[AdminHomePage])
Комментарии:
1. Я также нервничаю из-за последствий, поэтому придерживаюсь ответа Митча, но спасибо, что показали, что это было возможно, поскольку это позволило мне написать несколько тестов, зная, что я смогу их проанализировать.