Неявный параметр конструктора абстрактного класса и наследование в Scala

Я новичок в Scala и пытался изучить и понять неявные преобразования и параметры и столкнулся со сценарием, который меня сбивает с толку.

Для контекста я использую Scaldi для внедрения зависимостей в приложении Akka и хотел бы, чтобы несколько внедряемых акторов унаследовали от абстрактного класса. Я считаю, что не могу сделать абстрактный класс чертой именно потому, что нам нужно сделать неявный Injector доступным через аргумент конструктора, чтобы воспользоваться преимуществами фреймворка.

Очень надуманный пример, демонстрирующий поведение, которое я наблюдаю, выглядит следующим образом:

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

Я ожидал, что все заработает, но вместо этого получаю ошибку компиляции:

Unspecified value parameter secretSauce.
class Concrete extends Base {
             ^

Если я добавлю неявный параметр к конкретному классу, например, все заработает:

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

Я думаю, что моя путаница связана с тем, как работают неявные параметры - в ситуациях, подобных той, которую я описываю, не наследуются ли они дочерними классами? Может ли кто-нибудь ELI5, что происходит в моем примере, или указать мне ссылку, которая может помочь прояснить ситуацию?

Спасибо!


person simonl    schedule 07.12.2015    source источник


Ответы (2)


Точные правила, которые определяют, где компилятор Scala ищет неявные значения, довольно сложны, но в большинстве ситуаций вам нужно подумать только о двух местах, откуда могут поступать неявные значения:

  1. Текущий объем.
  2. Сопутствующие объекты для любых задействованных типов.

Это означает, что это будет компилироваться:

class SecretSauce {}

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

abstract class Base(implicit secretSauce: SecretSauce) {}

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

Или это:

class SecretSauce {}

abstract class Base(implicit secretSauce: SecretSauce) {}

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

  class Concrete extends Base {}
}

Однако в вашей версии, когда компилятор доходит до этой строки:

class Concrete extends Base {}

Он будет знать, что ему нужно найти неявное значение SecretSauce, и сначала посмотрит неявные значения в области в этой строке, а затем в сопутствующем объекте SecretSauce (если он существует). Он тоже не находит, поэтому отказывается компилировать ваш код.

person Travis Brown    schedule 07.12.2015
comment
Ах, я думаю, что перебор правил немного помогает. Почему не возникает ошибка компиляции в случае, когда неявный аргумент дублируется в конкретном классе? например class Concrete(implicit secretSauce: SecretSauce) extends Base {} Судя по тому, как я установил это в своем коде, неявное значение все еще не входит в область видимости и не входит в сопутствующий объект, верно? Мое понимание объема здесь отключено? - person simonl; 08.12.2015
comment
@simonl Когда вы добавляете неявный параметр в конструктор Concrete, этот неявный параметр будет в области видимости для вызова родительского конструктора. Синтаксис Scala для определений классов и конструкторов делает это немного неинтуитивным, но это работает. - person Travis Brown; 08.12.2015

Неявный параметр "разрешается" из:

  • Последствия, определенные в текущей области
  • Явный импорт
  • Импорт подстановочных знаков

Насколько я понимаю, для определения class Concrete необходимо определить или импортировать неявное.

Я нахожу очень хорошее объяснение в этом SO-ответе.

person Filippo Vitale    schedule 07.12.2015