How to test type conformance of higher-kinded types in Scala

后端 未结 2 1647
情歌与酒
情歌与酒 2021-02-04 14:40

I am trying to test whether two \"containers\" use the same higher-kinded type. Look at the following code:

import scala.reflect.runtime.universe._

class Funct[         


        
相关标签:
2条回答
  • 2021-02-04 14:58

    It's the mixup between underscores used in type parameter definitions and elsewhere. The underscore in TypeTag[B[_]] means an existential type, hence you get a tag not for B, but for an existential wrapper over it, which is pretty much useless without manual postprocessing.

    Consequently typeOf[Funct[B, _]] that needs a tag for raw B can't make use of the tag for the wrapper and gets upset. By getting upset I mean it refuses to splice the tag in scope and fails with a compilation error. If you use weakTypeOf instead, then that one will succeed, but it will generate stubs for everything it couldn't splice, making the result useless for subtyping checks.

    Looks like in this case we really hit the limits of Scala in the sense that there's no way for us to refer to raw B in WeakTypeTag[B], because we don't have kind polymorphism in Scala. Hopefully something like DOT will save us from this inconvenience, but in the meanwhile you can use this workaround (it's not pretty, but I haven't been able to come up with a simpler approach).

    import scala.reflect.runtime.universe._
    
    object Test extends App {
      class Foo[B[_], T]
      // NOTE: ideally we'd be able to write this, but since it's not valid Scala
      // we have to work around by using an existential type
      // def test[B[_]](implicit tt: WeakTypeTag[B]) = weakTypeOf[Foo[B, _]]
      def test[B[_]](implicit tt: WeakTypeTag[B[_]]) = {
        val ExistentialType(_, TypeRef(pre, sym, _)) = tt.tpe
    
        // attempt #1: just compose the type manually
        // but what do we put there instead of question marks?!
        // appliedType(typeOf[Foo], List(TypeRef(pre, sym, Nil), ???))
    
        // attempt #2: reify a template and then manually replace the stubs
        val template = typeOf[Foo[Hack, _]]
        val result = template.substituteSymbols(List(typeOf[Hack[_]].typeSymbol), List(sym))
        println(result)
      }
      test[Option]
    }
    
    // has to be top-level, otherwise the substituion magic won't work
    class Hack[T]
    

    An astute reader will notice that I used WeakTypeTag in the signature of foo, even though I should be able to use TypeTag. After all, we call foo on an Option which is a well-behaved type, in the sense that it doesn't involve unresolved type parameters or local classes that pose problems for TypeTags. Unfortunately, it's not that simple because of https://issues.scala-lang.org/browse/SI-7686, so we're forced to use a weak tag even though we shouldn't need to.

    0 讨论(0)
  • 2021-02-04 15:17

    The following is an answer that works for the example I have given (and might help others), but does not apply to my (non-simplified) case.

    Stealing from @pedrofurla's hint, and using type-classes:

    trait ConfTest[A,B] {
      def conform: Boolean
    }
    
    trait LowPrioConfTest {
      implicit def ctF[A,B] = new ConfTest[A,B] { val conform = false }
    }
    
    object ConfTest extends LowPrioConfTest {
      implicit def ctT[A,B](implicit ev: A <:< B) =
        new ConfTest[A,B] { val conform = true }
    }
    

    And add this to Foo:

    def imp[B[_]](implicit ct: ConfTest[A,Funct[B,_]]) =
      println(ct.conform)
    

    Now:

    x.imp[Option] // --> true
    x.imp[List]   // --> false
    
    0 讨论(0)
提交回复
热议问题