Search code examples
scalaimplicit

Scala implicit parameters need to be repeated?


I have the following simple test-case:

trait Printable[T] {
  val string: String
  def print() = println("It works: " + string)
}

def foo[T](x: T)(implicit ev: T => Printable[T]) = {
  x.print()
}

implicit def doubleToPrint(x: Double): Printable[Double] = new Printable[Double] {
  override val string: String = x.toString
}

foo(2.0) // => It works: 2.0

However, creating a new function bar[T](x: T) which should call foo(x) raises an error that there is no implicit view available from T => Printable[T] :

// this does not work
def bar[T](x: T) = {
  println("Some other stuff")
  foo(x)
}

However, when adding the same parameter it works ofcourse:

// this does work
def bar[T](x: T)(implicit ev: T => Printable[T]) = {
  println("Some other stuff")
  foo(x)
}

It seems to me quite cumbersome to keep chaining these implicit parameters, because I was under the assumption that the foo function will start searching for the implicit conversion and not the function bar calling foo. As I understand the implicit def doubleToPrint should be in scope for foo also when it is being called from within foo.

What am I overlooking?

Additional question in response to the answer of dth

So I understand the answer, which is actually very logical. However, the solution of the context bounds does not work in the situation where foo and bar are both part of another trait, since context bounds are not allowed in traits:

// not allowed to do trait FooBar[T : Printable]
trait FooBar[T] {
    def foo(x: T)(implicit ev: T => Printable[T]) = {
        println("Running foo...")
        x.print()
    }

    // this is not allowed
    def bar(x: T) = {
        println("But running bar first...")
        foo(x)
    }
}

So is it also possible to solve this without using the implicit parameter?

My own, not so nice, solution

I came to the conclusion that I actually don't need a trait in my specific code and can replace the FooBar[T] trait with an abstract class FooBar[T <% Printable[T]].

It is a solution for my problem, but not the additional question I added.


Solution

  • Think about, how implicit parameters work:

    When you call a method requiring an implicit parameter somewhere the compiler looks in the context (scope, involved types) for a suitable implicit.

    Now look at your definition of bar:

    def bar[T](x: T) = {
      foo(x)
    }
    

    When you call foo the compiler looks for an implicit value of the type T => Printable[T] which it cannot find. It does not matter, that you will call bar later with a type Double as it does not know that.

    So the answer is yes, you have to pass implicit parameters around everywhere, where you can not find a suitable value in the context (so usually everywhere, where you do not know the concrete type).

    There is however syntactic sugar called context bounds, so you can define bar like this:

    def bar[T: Printable](x: T) = foo(x)
    

    You could also define foo like this and use implicitly[Printable[T]] to access the values. So you do not have to concern yourself with implicit parameters at all in simple setting like this.

    Traits

    Context bounds are just syntactic sugar for an implicit parameter. The implicit value is passed where you define the type bound. I.e. if it is the type parameter of a method it is passed to the method.

    A trait may not have any constructor parameters as due to linearisation you do not no where it will end up in the inheritance hierarchy. Thus it may not have any implicit parameters or type bounds either.

    So you really have to add an implicit parameter to both methods. If you are implementing some complex API this is fine, if this appears a lot in user code, maybe you can change your design.

    Classes

    You can use context bounds for type parameters of classes, however the implicit parameter will not be directly used inside of methods of that class. To achieve that you have to provide a local implicit conversion like this:

    class FooBar[T: Printable] {
    
        implicit def conv(x: T) = implicitly[Printable[T]]
    
        def foo(x: T) = {
            println("Running foo...")
            x.print()
        }
        def bar(x: T) = {
            println("But running bar first...")
            foo(x)
        }
    }
    

    View Bounds

    There are also view bounds as an alternative. They have however been deprecated. If you run scalac with -Xfuture it will show you that.