Scala: согласование классов типа с внедрением зависимости

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

case class Wotsit (value: Int)

может быть адаптирован к черте Foo:

trait Foo[T] {
  def write (t: T): Unit
}

с помощью этого класса типа:

implicit object WotsitIsFoo extends Foo[Wotsit] {
  def write (wotsit: Wotsit) = println(wotsit.value)
}

Класс типа обычно получается во время компиляции с implicts, разрешая и Wotsit и его классу типа быть переданным вместе в функцию высшего порядка:

def writeAll[T] (items: List[T])(implicit tc: Foo[T]) =
  items.foreach(w => tc.write(w))

writeAll(wotsits)

(перед исправлением меня я сказал, что это был упрощенный пример),

Однако использование implicits предполагает, что точный тип объектов известен во время компиляции. Я нахожу в своем коде, это часто не имеет место: Я буду иметь список некоторого типа Списка объекта [T] и должен обнаружить, что корректный класс типа работает над ними.

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

Обычно это - то, где внедрение зависимости вступило бы, пользуясь библиотекой для предоставления требуемого объекта в точке оно необходимо. Детали меняются в зависимости от библиотеки, выбранной для DI - я записал свое собственное в Java в прошлом - но обычно точка инжекционных потребностей определить точно желаемый объект.

Проблема, в случае класса типа, точное значение не известно во время компиляции. Это должно быть выбрано на основе полиморфного описания. И кардинально, информация о типе была стерта компилятором. Декларации являются решением Scala ввести стирание, но мне совсем не ясно, как использовать их для решения этой проблемы.

Какие методы и библиотеки внедрения зависимости для Scala люди предложили бы в качестве способа заняться этим? Я пропускаю прием? Идеальная библиотека DI? Или это - действительно камень преткновения, которым это кажется?


Разъяснение

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

Во втором случае две точки разделяются барьером - таким как API, который не может быть изменен, или сохраненный в базе данных или объектно-ориентированной памяти, или сериализирован и отправить к другому компьютеру - который означает, что класс типа не может быть передан наряду с его операндом. В этом случае, учитывая объект, тип которого и значение известны только во времени выполнения, потребности класса типа так или иначе, чтобы быть обнаруженными.

Я думаю, что у функциональных программистов есть привычка к принятию первого случая - что с достаточно усовершенствованным языком, тип операнда всегда будет узнаваем. David и mkniessl предоставили хорошие ответы для этого, и я, конечно, не хочу критиковать их. Но второй случай определенно существует, и вот почему я принес внедрение зависимости в вопрос.

17
задан Marcus Downing 17 July 2010 в 12:31
поделиться

2 ответа

Изрядную часть утомительности передачи этих неявных зависимостей можно уменьшить, используя новый синтаксис контекстной привязки. Ваш пример становится

def writeAll[T:Foo] (items: List[T]) =
  items.foreach(w => implicitly[Foo[T]].write(w))

, который компилируется идентично, но дает красивые и четкие подписи и имеет меньше плавающих переменных "шума".

Не лучший ответ, но альтернативы, вероятно, включают рефлексию, и я не знаю ни одной библиотеки, которая просто заставила бы это работать автоматически.

13
ответ дан 30 November 2019 в 13:20
поделиться

(Я заменил имена в вопросе, они не помогли мне обдумать проблему)

Я буду решать проблему в два этапа. Сначала я покажу, как вложенные диапазоны позволяют избежать необходимости объявлять параметр класса типа на всем пути его использования. Затем я покажу вариант, в котором экземпляр класса типа "инжектируется зависимостью".

Экземпляр класса типа как параметр класса

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

object TypeClassDI1 {

  // The type class
  trait ATypeClass[T] {
    def typeClassMethod(t: T): Unit
  }

  // Some data type
  case class Something (value: Int)

  // The type class instance as implicit
  implicit object SomethingInstance extends ATypeClass[Something] {
    def typeClassMethod(s: Something): Unit =
      println("SomthingInstance " + s.value)
  }

  // A method directly using the type class
  def writeAll[T:ATypeClass](items: List[T]) =
    items.foreach(w => implicitly[ATypeClass[T]].typeClassMethod(w))

  // A class defining a scope with a type class instance known to be available    
  class ATypeClassUser[T:ATypeClass] {

    // bar only indirectly uses the type class via writeAll
    // and does not declare an implicit parameter for it.
    def bar(items: List[T]) {
      // (here the evidence class parameter defined 
      // with the context bound is used for writeAll)
      writeAll(items)
    }
  }

  def main(args: Array[String]) {
    val aTypeClassUser = new ATypeClassUser[Something]
    aTypeClassUser.bar(List(Something(42), Something(4711)))
  }
}

Экземпляр класса типа как поле, доступное для записи (инъекция сеттера)

Вариант вышеописанного, который можно использовать с помощью инъекции сеттера. На этот раз экземпляр класса типа передается через вызов сеттера в боб, использующий класс типа.

object TypeClassDI2 {

  // The type class
  trait ATypeClass[T] {
    def typeClassMethod(t: T): Unit
  }

  // Some data type
  case class Something (value: Int)

  // The type class instance (not implicit here)
  object SomethingInstance extends ATypeClass[Something] {
    def typeClassMethod(s: Something): Unit =
      println("SomthingInstance " + s.value)
  }

  // A method directly using the type class
  def writeAll[T:ATypeClass](items: List[T]) =
    items.foreach(w => implicitly[ATypeClass[T]].typeClassMethod(w))

  // A "service bean" class defining a scope with a type class instance.
  // Setter based injection style for simplicity.
  class ATypeClassBean[T] {
    implicit var aTypeClassInstance: ATypeClass[T] = _

    // bar only indirectly uses the type class via writeAll
    // and does not declare an implicit parameter for it.
    def bar(items: List[T]) {
      // (here the implicit var is used for writeAll)
      writeAll(items)
    }
  }

  def main(args: Array[String]) {
    val aTypeClassBean = new ATypeClassBean[Something]()

    // "inject" the type class instance
    aTypeClassBean.aTypeClassInstance = SomethingInstance

    aTypeClassBean.bar(List(Something(42), Something(4711)))
  }
}

Обратите внимание, что второе решение имеет общий недостаток инъекции на основе сеттера - вы можете забыть установить зависимость и получить красивое NullPointerException при использовании...

11
ответ дан 30 November 2019 в 13:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: