Search code examples
scalafunctionimplicit

Impicit function can't be injected


I have the following code:

case class Number (value:Int)

and

class Calculator {
  def performCalc( input:Number)(implicit calc: (Number=>Number) ) =  calc(input)
}

Now, when I, in a specs2 test try this:

class CalculatorTest extends mutable.Specification {
  "Calculator" should {
    "*  Accept explicit calculation parameter" in {
      implicit val addTwelve = (input: Number) => Number(input.value + 12)
      val calc = new Calculator()

      val result = calc.performCalc(Number(4))
      result must beEqualTo(16)
    }
  }
}

I expected the 'addTwelve' function to be injected implicitly as a parameter of performCalc. However, I get the following failure:

Error:(49, 42) ambiguous implicit values:
 both method $conforms in object Predef of type [A]=> <:<[A,A]
 and value addTwelve of type nl.example.Number => nl.example.Number
 match expected type nl.example.Number => nl.example.Number
      val result = calc.performCalc(Number(4))
                             ^

What am I doing wrong? It should be possible to use methods as implicits, right?

Scala: 2.11.7


Solution

  • Yes this is technically a valid use of implicit, but it's not a very strong use case. Specifically, there is a pre-existing implicit that provides Number=>Number. The compiler is having trouble telling which implicit method you really want.

    What's better is to wrap this method into a trait as a "tag" for the implicit type.

    case class Number(value: Int)
    trait CalcMethod {
      def perform(n: Number): Number
    }
    class Calculator {
      def performCalc(input:Number)(implicit calc: CalcMethod) = calc.perform(input)
    }
    
    class CalculatorTest extends mutable.Specification {
      "Calculator" should {
        "*  Accept explicit calculation parameter" in {
          implicit val addTwelve: CalcMethod = new CalcMethod { 
              def perform(input: Number) = Number(input.value + 12) 
          }
          val result = new Calculator().performCalc(Number(4))
          result must beEqualTo(16)
        }
      }
    }
    

    EDIT:

    This is maybe closer to what you want:

    case class Number(value: Int)
    implicit class CalcMethod(val perform: Number => Number)
    class Calculator {
      def performCalc(input:Number)(implicit calc: CalcMethod) = calc.perform(input)
    }
    

    Then you can use it like so:

    implicit val addTwelve: CalcMethod = (input: Number) => Number(input.value + 12) 
    val result = new Calculator().performCalc(Number(4))