Подтвердить что ты не робот

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

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

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

writeAll(wotsits)

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

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

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

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

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

Какие методы и библиотеки вложений для инъекций для Scala могли бы предложить люди как способ решения этой проблемы? Мне не хватает трюка? Идеальная библиотека DI? Или это действительно точка прилипания, которая кажется?


Разъяснение

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

Введите экземпляр класса как записываемое поле (инъекция установщика)

Вариант выше, который можно использовать с использованием инъекции сеттера. На этот раз экземпляр класса класса передается через вызов setter в bean с использованием класса type.

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