Как я могу перегрузить метод Scala типом блока? - PullRequest
1 голос
/ 09 июня 2011

Я все еще работаю над своим ScalaTest FeatureSpec 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

Как можно устранить неоднозначность или написать единственный метод, который может различать блок (или его отсутствие)?

Ответы [ 2 ]

2 голосов
/ 10 июня 2011

Мне нравится решение @ 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 голос
/ 09 июня 2011

У меня есть решение, но я думаю, что оно может быть улучшено.

Я использовал один 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])
...