Scala:使用依赖注入协调类型类 [英] Scala: reconciling type classes with dependency injection

查看:213
本文介绍了Scala:使用依赖注入协调类型类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于类型类模式,Scala博客中似乎有很多的热情,其中一个简单的类具有通过符合某些特征或模式的附加类添加到其中的功能。作为一个非常简单的例子,简单的类:

 案例类Wotsit(value:Int)

可以适应Foo特征:

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

在这个类型的帮助下:

 隐含对象WotsitIsFoo扩展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的解决方案来删除,但是对于我来说,如何使用它们来解决这个问题,这是非常明确的。



人们建议使用什么技术和依赖注入库作为一种解决这个问题的方法?我错过了一个技巧吗?完美的DI图书馆?或者这真的是粘贴点吗?






澄清



<我认为这真的有两个方面。在第一种情况下,需要类型类的点是通过直接函数调用从操作数的确切类型已知的地方获得的,因此足够的类型争吵和句法糖可以允许类型类被传递到指出它是必需的。



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



我认为功能性程序员有习惯第一种情况 - 使用足够先进的语言,操作数的类型将永远是可以知道的。 David和mkniessl为此提供了很好的答案,我当然不想批评那些。但是第二种情况确实存在,这就是为什么我将依赖注入带入问题。

解决方案

相当多的乏味通过使用新的上下文绑定语法可以减少传递这些隐式依赖关系。你的例子成为

  def writeAll [T:Foo](items:List [T])= 
items.foreach (w =>隐含地[Foo [T]]。write(w))

但是使得很好而清楚的签名,并且有更少的噪音变量浮动。



不是一个很好的答案,但替代方案可能涉及反射,我不知道的任何图书馆,只会使其自动工作。


There seems to be a lot of enthusiasm among Scala bloggers lately for the type classes pattern, in which a simple class has functionality added to it by an additional class conforming to some trait or pattern. As a vastly oversimplified example, the simple class:

case class Wotsit (value: Int)

can be adapted to the Foo trait:

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

with the help of this type class:

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

The type class is typically captured at compile time with implicts, allowing both the Wotsit and its type class to be passed together into a higher order function:

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

writeAll(wotsits)

(before you correct me, I said it was an oversimplified example)

However, the use of implicits assumes that the precise type of the items is known at compile time. I find in my code this often isn't the case: I will have a list of some type of item List[T], and need to discover the correct type class to work on them.

The suggested approach of Scala would appear to be to add the typeclass argument at all points in the call hierarchy. This can get annoying as an the code scales and these dependencies need to be passed down increasingly long chains, through methods to which they are increasingly irrelevant. This makes the code cluttered and harder to maintain, the opposite of what Scala is for.

Typically this is where dependency injection would step in, using a library to supply the desired object at the point it's needed. Details vary with the library chosen for DI - I've written my own in Java in the past - but typically the point of injection needs to define precisely the object desired.

Trouble is, in the case of a type class the precise value isn't known at compile time. It must be selected based on a polymorphic description. And crucially, the type information has been erased by the compiler. Manifests are Scala's solution to type erasure, but it's far from clear to me how to use them to address this issue.

What techniques and dependency injection libraries for Scala would people suggest as a way of tackling this? Am I missing a trick? The perfect DI library? Or is this really the sticking point it seems?


Clarification

I think there are really two aspects to this. In the first case, the point where the type class is needed is reached by direct function calls from the point where the exact type of its operand is known, and so sufficient type wrangling and syntactic sugar can allow the type class to be passed to the point it's needed.

In the second case, the two points are separated by a barrier - such as an API that can't be altered, or being stored in a database or object store, or serialised and send to another computer - that means the type class can't be passed along with its operand. In this case, given an object whose type and value are known only at runtime, the type class needs somehow to be discovered.

I think functional programmers have a habit of assuming the first case - that with a sufficiently advanced language, the type of the operand will always be knowable. David and mkniessl provided good answers for this, and I certainly don't want to criticise those. But the second case definitely does exist, and that's why I brought dependency injection into the question.

解决方案

A fair amount of the tediousness of passing down those implicit dependencies can be alleviated by using the new context bound syntax. Your example becomes

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

which compiles identically but makes for nice and clear signatures and has fewer "noise" variables floating around.

Not a great answer, but the alternatives probably involve reflection, and I don't know of any library that will just make this automatically work.

这篇关于Scala:使用依赖注入协调类型类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆