Scala: согласование классов типов с внедрением зависимостей - PullRequest
17 голосов
/ 15 июля 2010

В последнее время среди блоггеров 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)
}

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

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

writeAll(wotsits)

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

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

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

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

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

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


Разъяснение

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

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

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

Ответы [ 3 ]

13 голосов
/ 15 июля 2010

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

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

, который компилируется одинаково, но делает для хороших и четких подписей и имеет меньше «шумовых» переменных, плавающих вокруг.и я не знаю ни одной библиотеки, которая бы просто делала это автоматически.

11 голосов
/ 15 июля 2010

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

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

Введите экземпляр класса в качестве параметра класса

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

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

Тип экземпляра класса в качестве доступного для записи поля (внедрение сеттера)

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

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 при использовании ...

0 голосов
/ 29 марта 2018

Аргумент против классов типов как внедрение зависимостей здесь заключается в том, что в случае классов типов «точный тип элементов известен во время компиляции», тогда как в случае внедрения зависимостей они не известны. Возможно, вас заинтересует этот проект переписывания Scala, в котором я перешел от шаблона тортов к классам типов для внедрения зависимостей. Посмотрите на этот файл, где неявные объявления сделаны . Обратите внимание, как использование переменных среды определяет точный тип? Таким образом вы можете согласовать требования времени компиляции классов типов с потребностями внедрения зависимостей во время выполнения.

...