Is it possible to find a common supertype on type-system level in Scala?

前端 未结 3 1467
梦谈多话
梦谈多话 2020-12-19 02:53

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 some

相关标签:
3条回答
  • 2020-12-19 03:30

    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
    
    0 讨论(0)
  • 2020-12-19 03:37

    (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.

    0 讨论(0)
  • 2020-12-19 03:49

    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.

    0 讨论(0)
提交回复
热议问题