Scala:使用依赖注入协调类型类

Mar*_*ing 17 dependency-injection scala implicit typeclass

Scala博客最近对类型类模式似乎有很多热情,其中一个简单的类通过符合某些特征或模式的附加类添加了功能.作为一个极其简单的例子,简单的类:

case class Wotsit (value: Int)
Run Code Online (Sandbox Code Playgroud)

可以适应Foo的特点:

trait Foo[T] {
  def write (t: T): Unit
}
Run Code Online (Sandbox Code Playgroud)

借助此类型类:

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

类型类通常在编译时捕获,带有隐含,允许Wotsit及其类型类一起传递到更高阶函数:

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

writeAll(wotsits)
Run Code Online (Sandbox Code Playgroud)

(在你纠正我之前,我说这是一个过于简单的例子)

但是,使用implicits假定在编译时已知项的精确类型.我发现在我的代码中经常不是这样的:我将列出某种类型的项目List [T],并且需要发现正确的类型类来处理它们.

Scala的建议方法似乎是在调用层次结构的所有点添加类型类参数.这可能会因为代码规模而变得烦人,并且这些依赖关系需要通过越来越长的链传递,通过它们越来越无关紧要的方法.这使得代码变得混乱并且难以维护,与Scala的相反.

通常,这是依赖注入将介入的地方,使用库在需要的时间点提供所需的对象.详细信息因为DI选择的库而异 - 我过去在Java中编写了自己的库 - 但通常注入点需要精确定义所需的对象.

麻烦的是,在类型类的情况下,在编译时不知道精确值.必须根据多态描述选择它.而且至关重要的是,类型信息已被编译器删除.清单是Scala的类型擦除解决方案,但我不清楚如何使用它们来解决这个问题.

Scala的哪些技术和依赖注入库会被人们建议作为解决这个问题的方法?我错过了一招吗?完美的DI库?或者这真的是它似乎的关键点吗?


澄清

我认为这有两个方面.在第一种情况下,需要类型类的点是通过直接函数调用从其操作数的确切类型已知的点到达的,因此足够的类型争论和语法糖可以允许类型类传递给指出它是必要的.

在第二种情况下,这两个点由屏障隔开 - 例如无法更改的API,或存储在数据库或对象存储中,或序列化并发送到另一台计算机 - 这意味着类型类可以' t与其操作数一起传递.在这种情况下,给定一个只在运行时知道其类型和值的对象,需要以某种方式发现类型类.

我认为函数式程序员习惯于假设第一种情况 - 使用足够先进的语言,操作数的类型总是可以知道的.David和mkniessl为此提供了很好的答案,我当然不想批评这些.但第二种情况确实存在,这就是我将依赖注入带入问题的原因.

Dav*_*ith 13

通过使用新的上下文绑定语法,可以减轻传递这些隐式依赖关系的相当繁琐的繁琐程度.你的榜样变成了

def writeAll[T:Foo] (items: List[T]) =
  items.foreach(w => implicitly[Foo[T]].write(w))
Run Code Online (Sandbox Code Playgroud)

它具有相同的编译方式,但可以实现漂亮和清晰的签名,并且具有较少的"噪声"变量.

不是一个很好的答案,但替代方案可能涉及反思,我不知道任何库只会让它自动运行.

  • 这对麻醉剂很有帮助,即使它不能治愈任何东西:) (2认同)

mkn*_*ssl 11

(我已经在问题中替换了名字,他们没有帮助我思考这个问题)

我将分两步攻击这个问题.首先,我展示了嵌套作用域如何避免必须在其使用中一直声明类型类参数.然后我将展示一个变体,其中类型类实例是"依赖注入".

键入类实例作为类参数

为了避免在所有中间调用中将类型类实例声明为隐式参数,可以在定义特定类型类实例应该可用的范围的类中声明类型类实例.我正在使用快捷语法("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)))
  }
}
Run Code Online (Sandbox Code Playgroud)

将类实例键入可写字段(setter injection)

上述的变体可以使用二次注射器使用.这次类型类实例通过setter调用使用类型类传递给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)))
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,第二个解决方案具有基于setter的注入的常见缺陷,您可以忘记设置依赖项并在使用时获得良好的NullPointerException ...