Как я могу перегрузить метод Scala по типу блока?

#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. Я также нервничаю из-за последствий, поэтому придерживаюсь ответа Митча, но спасибо, что показали, что это было возможно, поскольку это позволило мне написать несколько тестов, зная, что я смогу их проанализировать.