9

Is it possible to make a type-alias (or something equivalent) in Scala that takes two parameters and returns their common supertype? In other words, I'm trying to find something with this signature:

type CommonSupertype[A, B] // can't be more specific

where these hold true: (pseudocode)

CommonSupertype[String, Int] = Any
CommonSupertype[JButton, JPanel] = JComponent

etc.

I haven't been able to find it myself, but I can't use alternatives like adding an extra parameter, since I have to follow a pre-specified interface.

Anonymous
  • 821
  • 1
  • 5
  • 14

3 Answers3

4

(Not a complete solution, but might give some ideas)

One impressive feature of Scala is its ability to return a list of Fruits when an orange is appended to a list of apples. It's fine with values, precisely because you let the generic type be inferred.

import scala.reflect.Manifest

def CommonSuperType[A, B >: A : Manifest](a:A, b:B) = manifest[B]  

It works (kind of) :

scala> CommonSuperType(new JButton, new JPanel)
res42: Manifest[javax.swing.JComponent with javax.accessibility.Accessible] = javax.swing.JComponent with javax.accessibility.Accessible

Next step would be to lift this trick to higher kinded types (not tested).
An half baked solution consists in creating values from types (cf this answer) :

class CommonSuper[A:Manifest, B:Manifest] {
   def make[T:Manifest] = manifest[T].erasure.newInstance.asInstanceOf[T]
   val instanceA = make[A]
   val instanceB = make[B]
   def getType = CommonSuperType(instanceA, instanceB)
}   

But I'm stuck in this unintuitive inconsistency :

scala> val test = new CommonSuper[JButton, JPanel]

scala> test.getType
res66: Manifest[Any] = Any

scala> CommonSuperType(test.instanceA, test.instanceB)
res67: Manifest[javax.swing.JComponent with javax.accessibility.Accessible] = javax.swing.JComponent with javax.accessibility.Accessible

Anyway, whereas I'm fond of this type of questions (questions about types), here it smells like an XY Problem.

Community
  • 1
  • 1
YvesgereY
  • 3,778
  • 1
  • 20
  • 19
2

I can give you a trait CommonSupertype[A, B] and an implicit generator function, so you can just require an implicit instance of this trait wherever you need it, and it will contain the common supertype (as a dependent type).

This is not my idea, it's actually adapted ever so slightly from this post by Miles Sabin.

The only change I've made is that, while he is using ¬¬[C] <:< (A ∨ B) as evidence that a type C is a subtype of either A or B, I've reversed the subtyping direction (so: (A ∨ B) <:< ¬¬[C]) to check that both A and B are subtypes of C.

import scala.reflect.ClassTag

object Main extends App {
  type ¬[A] = A => Nothing
  type ∨[T, U] = ¬[¬[T] with ¬[U]]
  type ¬¬[A] = ¬[¬[A]]

  trait CommonSupertype[A, B] {
    type λ
    def tag: ClassTag[λ]
  }
  // The ClassTag is only so I can get ahold of the type's name at runtime
  implicit def commonSupertype[A, B, C : ClassTag](implicit C: (A ∨ B) <:< ¬¬[C]): CommonSupertype[A, B] { type λ = C } =
    new CommonSupertype[A, B] { type λ = C; def tag = implicitly[ClassTag[C]] }

  trait Pet
  class Dog extends Pet
  class Cat extends Pet
  def check[A, B](implicit x: CommonSupertype[A, B]) = {
    // This just prints the common type, but you could use the type (x.λ) directly
    println(x.tag.toString())
  }
  check[Dog, Cat]
  check[Dog, Double]
}

Gives us:

Main.Pet
Any
Dan
  • 3,490
  • 2
  • 22
  • 27
1

You can obtain typeTag of least common supertype and then extract its type (see How to capture T from TypeTag[T] or any other generic in scala?)

import scala.reflect.runtime.universe._
import scala.util._

def t[A, B] = (null.asInstanceOf[A], null.asInstanceOf[B])
implicit class RichTuple[A: TypeTag](a: (A, A)) {def common = typeTag[A]}

implicit class RichT[T: TypeTag](a: T) {//just helper for working with typetags 
   def asInstanceOfT[U](t: TypeTag[U]) = a.asInstanceOf[U]
   def hasSameTypeWith[U](t: TypeTag[U]) = typeTag[T] == t
}

Usage

scala> t[String, String].common
res87: reflect.runtime.universe.TypeTag[String] = TypeTag[String]

scala> t[String, Int].common
res88: reflect.runtime.universe.TypeTag[Any] = TypeTag[Any]

scala> ("aa" : String).hasSameTypeWith(t[String, String].common)
res105: Boolean = true

scala> ("aa" : String).hasSameTypeWith(t[String, Int].common)
res106: Boolean = false

scala> ("aa" : String).asInstanceOfT(t[String, Int].common)
res109: Any = aa 

scala> ("aa" : String).asInstanceOfT(t[Int, Int].common)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

For isInstanceOf see How to know if an object is an instance of a TypeTag's type?

The only restriction is that you can't obtain common supertype of type parameters - it will always be erased to Any.

Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88