Кажется, существует большой энтузиазм среди блоггеров 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 предоставили хорошие ответы для этого, и я, конечно, не хочу критиковать их. Но второй случай определенно существует, и вот почему я принес внедрение зависимости в вопрос.
Изрядную часть утомительности передачи этих неявных зависимостей можно уменьшить, используя новый синтаксис контекстной привязки. Ваш пример становится
def writeAll[T:Foo] (items: List[T]) =
items.foreach(w => implicitly[Foo[T]].write(w))
, который компилируется идентично, но дает красивые и четкие подписи и имеет меньше плавающих переменных "шума".
Не лучший ответ, но альтернативы, вероятно, включают рефлексию, и я не знаю ни одной библиотеки, которая просто заставила бы это работать автоматически.
(Я заменил имена в вопросе, они не помогли мне обдумать проблему)
Я буду решать проблему в два этапа. Сначала я покажу, как вложенные диапазоны позволяют избежать необходимости объявлять параметр класса типа на всем пути его использования. Затем я покажу вариант, в котором экземпляр класса типа "инжектируется зависимостью".
Чтобы избежать необходимости объявлять экземпляр класса типа как неявный параметр во всех промежуточных вызовах, можно объявить экземпляр класса типа в классе, определяющем область видимости, в которой должен быть доступен конкретный экземпляр класса типа. Я использую синтаксис сокращения ("контекстная привязка") для определения параметра класса.
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 при использовании...