erdavila erdavila - 1 month ago 8
Scala Question

Define common lower bounds of parameters

In the following function, the Scala compiler is able to define the return type to the lowest common supertype of the values used in the if/else expression:

def cond(b: Boolean, t: A, f: B) = if (b) t else f


Considering the following hierarchy:

class X
class A extends X
class B extends X


the function
cond
above is defined as returning a value of type
X
.

However, if
A
and
B
are type parameters in the definition of the
cond
function, its return type is
Any
:

def cond[A, B](b: Boolean, t: A, f: B) = if (b) t else f


Is it possible to make the compiler use the lowest common supertype of the type parameters?

I tried some variations of the following, without success:

def cond[A, B, R >: A with B](b: Boolean, t: A, f: B): R = if (b) t else f
def cond[A, B, R](b: Boolean, t: A, f: B)(implicit ev1: A <:< R, ev2: B <:< R): R = if (b) t else f


EDIT: The question above is oversimplified. In fact my real problem had one of the type parameters already resolved:

class C[A](b: Boolean, t: A) {
def cond[B](f: B) = if(b) t else f
}

Answer

If you don't need the exacta types of the arguments, then the following will usually suffice:

def cond[T](b: Boolean, t: T, f: T) = if (b) t else f

Scala will automatically upcast the types of the arguments to their least upper bound (LUB):

scala> cond(true, new A, new B)
res0: X = A@74a59bb6

But if you need the exact types, for example for implicit resolution, I believe the following trick should work:

def cond[A, B, C >: A](b: Boolean, t: A, f: B with C): C = if (b) t else f

Here A and B are exact types of the arguments, and C is their LUB. C has the constraint to be the supertype of A: C >: A, but it also should be the type of the second argument, because it's defined as f: B with C, and thus it is inferred as the LUB of A and B.

We can check correct type inference with this definition with the following code:

import reflect.runtime.universe._
def cond[A, B, C >: A](b: Boolean, t: A, f: B with C)(
  implicit ta: TypeTag[A], 
           tb: TypeTag[B], 
           tc: TypeTag[C]
): C = {
  println(ta)
  println(tb)
  println(tc)
  if (b) t else f
}

scala> cond(true, new A, new B)
TypeTag[A]
TypeTag[B]
TypeTag[X]
res5: X = A@f0ad2ea
Comments