开发者

How can I overload a Scala method by block type?

开发者 https://www.devze.com 2023-03-11 23:21 出处:网络
I\'m still working on my ScalaTest FeatureSpec DSL. I\'d like 3 variants of my given function. All take tokens: Any, and then either

I'm still working on my ScalaTest FeatureSpec DSL.

I'd like 3 variants of my given function. All take tokens: Any, and then either

A. A block block: => Unit that is executed later

given("user visits", the[AdminHomePage]) {
    // just code 
}

B. A block block: Any => Unit that is executed later with the tokens

given("user visits", the[AdminHomePage]) { 
  x: Any => x match { 
    case ("user visits", pageClass:Class[Page]) => 
      startPage(pageClass)
  }
}

C. No block, where the tokens are processed by another function

given("user visits", the[AdminHomePage])

Now when I define all three methods

def given(tokens: Any) = ...
def given(tokens: Any)(block: Any => Unit) = block(tokens)
def given(tokens: Any)(block: => Unit) = block

The compiler considers them ambiguous.

ambiguous reference to overloaded definition, both method given in trait GivenWhenThenFeatureSpec of type (tokens: Any)(block: => Unit)Unit and  metho开发者_JAVA百科d given in trait GivenWhenThenFeatureSpec of type (tokens: Any)(block: (Any) => Unit)Unit match argument types

How can disambiguate, or write a single method that can differentiate between the block (or lack of)?


I like @MachAndy's solution above, except for the importing of the unit2emptyfunction conversions, which I see as possibly interfering or covering other type errors.

If instead you define the following:

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)
}

Then, you can simply:

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
  }
}


I have here a solution but I think it can be enhanced.

I used a single given method as entry and implicit to provide or not a body

def given[A](tokens: A)(implicit block: A => Unit) {
    block(tokens)
}

First here is a sugar to be able use a Unit block as a Any => Unit

implicit def unit2emptyfunction(body: Unit): Any => Unit = {
    case _ => body
}

To be able to work in the C case, I provide a default body to fill the block parameter that does nothing.

implicit val doNothing: Any => Unit = { }

Now you can use it in this way :

/*
 * 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])
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号