Reputation: 1802
Let's say I have a function such as:
def foo[T<:U <:V](t:T): Unit
I want to know if there is a way of combining these two in a type W
so that I can have:
def foo[T<: W](t: T): Unit
The use case for this is:
trait FooTrait{
type W
def foo[T<: W](t: T): Unit
}
I might have two different implementations of foo, one of them with a simple W1
type bound whereas the other one is T<:W1 <:W2
I want the more complex one to define a W3
so that I can have:
def foo[T<: W3](t: T): Unit
Similiarly, I want to be able to do these with type classes. So if I have:
def bar[T<:U :V](t:T): Unit
I want to have
def bar[T:X](t:T): Unit
The use case for this is essentially the same as the earlier case.
is this possible?
Upvotes: 1
Views: 1588
Reputation: 23788
Andrey Tyukin has correctly point out that with
is probably the answer for your first question and that the second question has no direct solution. However if you are OK with indirect solutions, you might work this around by introducing a new type class that will says that the target types belongs to the two original type classes. Here is a simple example for imaginary typeclasses Foo
and Bar
and for a base trait Base
with a specific implementation ChildFooBar
:
trait Foo[X] {
def doFoo(x: X): String
}
trait Bar[X] {
def doBar(x: X): String
}
trait Base {
type W[_]
def baz[T: W](t: T): String
}
class ChildFooBar extends Base {
import ChildFooBar._
type W[X] = FooAndBar[X]
override def baz[T: FooAndBar](t: T): String = {
val foo = implicitly[Foo[T]]
val bar = implicitly[Bar[T]]
foo.doFoo(t) + bar.doBar(t)
}
}
object ChildFooBar {
@implicitNotFound("The type should provide both implicit Foo and implicit Bar.")
case class FooAndBar[X](foo: Foo[X], bar: Bar[X])
object FooAndBar {
implicit def fromFooAndBar[X](implicit foo: Foo[X], bar: Bar[X]): FooAndBar[X] = FooAndBar(foo, bar)
}
// private is important here to avoid diversion of implicits
private implicit def toFoo[X](implicit fooAndBar: FooAndBar[X]): Foo[X] = fooAndBar.foo
private implicit def toBar[X](implicit fooAndBar: FooAndBar[X]): Bar[X] = fooAndBar.bar
}
Now if SomeClass
is a member of both Foo
and Bar
typeclasses, then
case class SomeClass(foo: Int, bar: Double)
object SomeClass {
implicit val foo: Foo[SomeClass] = new Foo[SomeClass] {
override def doFoo(x: SomeClass) = s"foo = ${x.foo}"
}
implicit val bar: Bar[SomeClass] = new Bar[SomeClass] {
override def doBar(x: SomeClass) = s"bar = ${x.bar}"
}
}
the simple code
println(new ChildFooBar().baz(SomeClass(1, 2.0)))
will compile and work as expected.
Upvotes: 3
Reputation: 44908
In the first part of your question, the syntax isn't even valid.
If you want to impose multiple upper bounds U
, V
on some type T
,
you have to use with
keyword anyway:
trait A
trait B
def f[X <: A with B](x: X): Unit = ???
This here doesn't work:
// def f[X <: A <: B](x: X): Unit = ??? // doesn't compile
To address the second part of your question, I would like to
explain why something like with
doesn't work for typeclasses.
This here does work:
trait Foo[X]
trait Bar[X]
def g[X: Foo : Bar](x: X): Unit = ???
This here doesn't work:
// def g[X: Foo with Bar](x: X): Unit = ??? // nope
Why? Because
def foo[X: Y](x: X): Ret = ???
is actually a shortcut for
def foo[X](x: X)(implicit y: Y[X]): Ret = ???
If you would try to somehow amalgamate two different typeclasses Foo
and Bar
,
this would result in the following desugared code:
def foo[X](x: X)(implicit superPowerfulThing: (Foo somehowGluedWith Bar)[X]): Ret = ???
But this is obviously not something that you should want. Instead, what you want is:
def foo[X](x: X)(implicit foo: Foo[X], bar: Bar[X])(x: X): Ret = ???
In this way, the two requirements Foo
and Bar
can be supplied independently,
which wouldn't work if you requested some superPowerfulThing
that implements
both Foo
and Bar
at once.
Hope that clarifies why something works or doesn't work.
Upvotes: 3