我想定义一个具有返回类型的方法,该类型依赖于传递给该方法的参数类型和封闭特征的类型参数。很难用语言描述我的意思,所以下面是一些要解释的片段。
我有几个构建块
// This is the target of a binding
trait BindableValue[T] {
def set(value: T): Unit
// this is the method I am strugling with
def bindTo[S](o: S) = ???
}
// This will dispatch events of type T
trait Observable[T]
// This represents a value of type T that will
// dispatch events if the value changes
trait ObservableValue[T] extends Observable[T] {
def value: T
}
// An Observable can be converted to an optional ObservableValue
object ObservableValue {
implicit def wrap[T](o:Observable[T]):ObservableValue[Option[T]] =
new ObservableValue[Option[T]] {
val value = None
}
}
绑定具有源和目标,并且存在两种类型:
...
trait Binding[S, T] {
def source: ObservableValue[S]
def target: BindableValue[T]
}
class CompleteBinding[T](
val source: ObservableValue[T],
val target: BindableValue[T]) extends Binding[T, T]
class IncompleteBinding[S, T](
val source: ObservableValue[S],
val target: BindableValue[T]) extends Binding[S, T]
我可以构建以下实例。
val bindable1 = new BindableValue[Int] { def set(value:Int) = {} }
val property1 = new ObservableValue[Int] { def value = 0 }
val property2 = new ObservableValue[String] { def value = "" }
val bindable2 = new BindableValue[Option[Int]] { def set(value:Option[Int]) = {} }
val event1 = new Observable[Int] {}
val event2 = new Observable[String] {}
现在我想使用这样的实例:
// 'a' should be of type CompleteBinding
val a = bindable1 bindTo property1
// 'b' should be of type IncompleteBinding
val b = bindable1 bindTo property2
// 'c' should be of type CompleteBinding
val c = bindable2 bindTo event1
// 'd' should be of type IncompleteBinding
val d = bindable2 bindTo event2
我无法弄清楚如何定义方法bindTo
,以便它将编译上述4行并为所有值设置正确的具体类型。我简直错过了有关Scala类型系统的知识。
尽管我很想找到一个解决方案,但我也想了解如何在未来找到这样的解决方案。如果您有针对上述问题的解决方案,您是否也可以向我指出一些我可以用来教育自己的资源?
答案 0 :(得分:3)
类型类可以帮助您解决问题。首先让我们定义简单的特征:
trait Composeable[A, B, R] {
def compose(a: A, b: B): R
}
只需要a
和b
并将其组合在R
类型的其他容器中。
现在让我们为伴侣对象中的CompleteBinding
和IncompleteBinding
定义它的两个具体实现,并使它们隐含:
object Composeable extends LowPriorityComposables {
implicit def comCommplete[T] = new Composeable[ObservableValue[T], BindableValue[T], CompleteBinding[T]] {
def compose(a: ObservableValue[T], b: BindableValue[T]) = new CompleteBinding(a, b)
}
}
trait LowPriorityComposables {
implicit def comIncommplete[S, T] = new Composeable[ObservableValue[S], BindableValue[T], IncompleteBinding[S, T]] {
def compose(a: ObservableValue[S], b: BindableValue[T]) = new IncompleteBinding(a, b)
}
}
如您所见,实施非常简单。它只需ObservableValue
和BindableValue
,然后在Binding
中合并。我将它们分成不同的特征,因为通常它们都可以使用,如果你观察并绑定某种类型T
的相同值(CompleteBinding
的情况)。通过在告诉编译器的单独特征中提取comIncommplete
,如果没有找到其他合适的隐式,它应该使用它。换句话说,您告诉编译器总是尝试应用CompleteBinding
,如果无法完成,则应该应用IncompleteBinding
。
唯一剩下的就是定义使用bindTo
类型类的Composeable
方法:
def bindTo[S, R](o: ObservableValue[S])(implicit ev: Composeable[ObservableValue[S], BindableValue[T], R]): R =
ev.compose(o, this)
在这里我说,第一个参数是o
,其中包含ObservableValue
类型的S
,但我也希望编译找到证据,存在一些隐含的,证明我可以用ObservableValue[S]
撰写BindableValue[T]
。当我有这样的证据时,我只是用这个(可绑定的)构成可观察的。正如您所看到的,类型参数R
类似于自由变量 - 编译器可以解决它本身,因此您始终具有从bindTo
方法返回的concreate类型信息。
现在所有测试用例都应该编译得很好:
val a: CompleteBinding[Int] = bindable1 bindTo property1
val b: IncompleteBinding[String, Int] = bindable1 bindTo property2
val c: CompleteBinding[Option[Int]] = bindable2 bindTo event1
val d: IncompleteBinding[Option[String], Option[Int]] = bindable2 bindTo event2