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.
I have a few building blocks
// 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:
- Complete: both source and target type parameters are the same
- Incomplete: source and target type parameters are different
…
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]
I can construct the following instances.
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
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 can help you to solve the problem. First let’s define simple trait:
It just takes
aandband composes them in some other container of typeR.Now let’s define 2 concrete implementations of it for
CompleteBindingandIncompleteBindingin companion object and also make them implicit:As you can see, implementation is pretty straightforward. It just takes
ObservableValueandBindableValueand combines then inBinding. I separated them in different traits because generally they both can be used, if you observe and bind the same value of some typeT(the case ofCompleteBinding). By extractingcomIncommpletein 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 applyCompleteBindingand if it cannot be done, thenIncompleteBindingshould be applied.The only thing remains is to define
bindTomethod that usesComposeabletype class:Here I say, that the first argument is
owhich is someObservableValuethat contains values of typeS, but I also want compile to find an evidence, that there exist some implicit, that proves, that I can composeObservableValue[S]withBindableValue[T]. When i have such evidence, I just compose observable with this (bindable). As you can see, type parameterRis something like free variable – compiler can figure it out but itself, so you always have concreate type information returned from thebindTomethod.Now all you test cases should compile just fine: