Maths noob
Maths noob

Reputation: 1802

Combining type bounds in Scala

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

Answers (2)

SergGr
SergGr

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

Andrey Tyukin
Andrey Tyukin

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

Related Questions