Implicit abstract class constructor parameter and inheritance in Scala
Asked Answered
M

2

7

I am fairly new to Scala and have been trying to learn and understand implicit conversions and parameters and have encountered a scenario that I find confusing.

For context, I am using Scaldi to do dependency injection in an Akka application and would like to have multiple injectable actors inherit from an abstract class. I believe I am unable to make the abstract class a trait precisely because we need to make an implicit Injector available via a constructor argument to take advantage of the framework.

A very contrived example that exhibits the behavior that I am seeing is as follows:

class SecretSauce {}

abstract class Base(implicit secretSauce: SecretSauce) {}

class Concrete extends Base {}

object Example extends App {
    ... // Setup Actor system, etc, etc
    implicit val secretSauce: SecretSauce = new SecretSauce()
}

I was expecting things to work but instead I get a compilation error:

Unspecified value parameter secretSauce.
class Concrete extends Base {
             ^

If I add the implicit parameter to the concrete class, like such, things work:

class Concrete(implicit secretSauce: SecretSauce) extends Base {}

I think my confusion stems from how implicit parameters work - in situations like the one I'm describing, are they not inherited by child classes? Can someone ELI5 what is occurring in my example or point me to a reference that can help clear things up?

Thanks!

Motivate answered 7/12, 2015 at 21:36 Comment(0)
L
4

The exact rules that determine where the Scala compiler looks for implicits are kind of complicated, but in most situations you only need to think about two places implicit values can come from:

  1. The current scope.
  2. The companion objects for any types involved.

This means this will compile:

class SecretSauce {}

object SecretSauce {
  implicit val secretSauce: SecretSauce = new SecretSauce()
}

abstract class Base(implicit secretSauce: SecretSauce) {}

object Example extends App {
  class Concrete extends Base {}
}

Or this:

class SecretSauce {}

abstract class Base(implicit secretSauce: SecretSauce) {}

object Example extends App {
  implicit val secretSauce: SecretSauce = new SecretSauce()

  class Concrete extends Base {}
}

In your version, though, when the compiler gets to this line:

class Concrete extends Base {}

It will know that it needs to find an implicit SecretSauce value, and it will look first at the implicit values in scope at that line and next in the SecretSauce companion object (if it exists). It doesn't find either, so it refuses to compile your code.

Loper answered 7/12, 2015 at 21:53 Comment(2)
Ahh, I think that distillation of the rules helps quite a bit. Why is a compilation error not thrown in the case where the implicit argument is duplicated in the concrete class? e.g class Concrete(implicit secretSauce: SecretSauce) extends Base {} From the way I've set it up in my code, the implicit value is still not in scope nor is it in a companion object, right? Is my understanding of scope here off?Motivate
@Motivate When you add the implicit parameter to the Concrete constructor, that implicit will be in scope for the call to the parent constructor. Scala's syntax for class definitions and constructors makes this a little unintuitive, but it works out.Loper
S
1

The implicit parameter get "resolved" from:

  • Implicits defined in current scope
  • Explicit imports
  • Wildcard imports

In order to define class Concrete as far as I understand, the implicit needs to be defined or imported.

I find a very good explanation in this SO answer.

Squarerigger answered 7/12, 2015 at 21:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.