本文介绍了如何定义返回类型基于 Scala 中的参数类型和类型参数的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想定义一个方法,它的返回类型取决于传递给该方法的参数类型和封闭特征的类型参数.很难用文字来描述我的意思,所以下面是一些片段来解释.

I want to define a method which has a returntype that is dependent on the type of the argument passed to that method and the type parameter of the enclosing trait. It's quite difficult to describe in words what I mean, so below are some snippets to explain.

我有一些积木

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

一个绑定有一个源和一个目标,有两种:

A binding has a source and a target and exists for two kinds:

  1. 完整:源和目标类型参数相同
  2. 不完整:源和目标类型参数不同

...

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] {}

现在我想像这样使用这些实例:

Now I want to use those instances like this:

// '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 类型系统的知识.

I can't figure out how to define the method bindTo so that it will compile the above 4 lines and have the correct concrete type for all values. I simply miss knowledge about the Scala type system.

虽然我很想找到一个解决方案,但我也想了解将来如何自己找到这样的解决方案.如果您对上述问题有解决方案,能否也给我指出一些我可以用来教育自己的资源?

Allthough I would love to find a solution I also want to understand how to get to such a solution myself in the future. If you have a solution for the above problem, could you also point me to some sources that I could use to educate myself?

推荐答案

Typeclasses 可以帮助您解决问题.首先让我们定义简单的特征:

Typeclasses can help you to solve the problem. First let's define simple trait:

trait Composeable[A, B, R] {
    def compose(a: A, b: B): R
}

它只需要 ab 并将它们组合在其他一些 R 类型的容器中.

It just takes a and b and composes them in some other container of type R.

现在让我们为伴随对象中的 CompleteBindingIncompleteBinding 定义 2 个具体的实现,并使它们隐式:

Now let's define 2 concrete implementations of it for CompleteBinding and IncompleteBinding in companion object and also make them implicit:

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

如您所见,实现非常简单.它只需要 ObservableValueBindableValue 然后在 Binding 中组合.我将它们分成不同的特征,因为通常它们都可以使用,如果您观察并绑定某种类型 T 的相同值(CompleteBinding 的情况).通过在单独的 trait 中提取 comIncommplete,我告诉编译器,如果没有找到其他合适的隐式,它应该使用它.换句话说,您告诉编译器始终尝试应用 CompleteBinding,如果无法完成,则应应用 IncompleteBinding.

As you can see, implementation is pretty straightforward. It just takes ObservableValue and BindableValue and combines then in Binding. I separated them in different traits because generally they both can be used, if you observe and bind the same value of some type T (the case of CompleteBinding). By extracting comIncommplete in separate trait I told compiler, that it should use it in case if no other suitable implicit was found. In other word you are telling compiler to always try to apply CompleteBinding and if it cannot be done, then IncompleteBinding should be applied.

剩下的就是定义使用 Composeable 类型类的 bindTo 方法:

The only thing remains is to define bindTo method that uses Composeable type class:

def bindTo[S, R](o: ObservableValue[S])(implicit ev: Composeable[ObservableValue[S], BindableValue[T], R]): R =
    ev.compose(o, this)

这里我说,第一个参数是 o,它是一些 ObservableValue 包含 S 类型的值,但我也想编译找到一个证据,证明存在一些隐含的,证明我可以用 BindableValue[T] 组合 ObservableValue[S].当我有这样的证据时,我只是用这个(可绑定的)组成可观察的.如您所见,类型参数 R 类似于自由变量 - 编译器可以自己解决它,因此您始终可以从 bindTo 方法返回 concreate 类型信息.

Here I say, that the first argument is o which is some ObservableValue that contains values of type S, but I also want compile to find an evidence, that there exist some implicit, that proves, that I can compose ObservableValue[S] with BindableValue[T]. When i have such evidence, I just compose observable with this (bindable). As you can see, type parameter R is something like free variable - compiler can figure it out but itself, so you always have concreate type information returned from the bindTo method.

现在你所有的测试用例都应该可以正常编译了:

Now all you test cases should compile just fine:

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

这篇关于如何定义返回类型基于 Scala 中的参数类型和类型参数的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 16:10