Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Path-dependent types and nested traits

Background

Suppose I have some nested traits:

trait Foo { trait Bar }

And a couple of instances:

val myFoo = new Foo {}
val myBar = new myFoo.Bar {}

I can write the following, which look (at least at a glance) like they should do more or less the same thing:

def whatever1(foo: Foo)(bar: foo.Bar) = bar
def whatever2(foo: Foo): foo.Bar => foo.Bar = { bar => bar }
def whatever3(foo: Foo) = new { def apply(bar: foo.Bar) = bar }
case class whatever4(foo: Foo) { def apply(bar: foo.Bar) = bar }
case class whatever5[F <: Foo](foo: F) { def apply(bar: foo.Bar) = bar }

Note that the last is inspired by the solution given here.

The first three work:

scala> val sameBar1: myFoo.Bar = whatever1(myFoo)(myBar)
sameBar1: myFoo.Bar = $anon$1@522f63e7

scala> val sameBar2: myFoo.Bar = whatever2(myFoo)(myBar)
sameBar1: myFoo.Bar = $anon$1@522f63e7

scala> val sameBar3: myFoo.Bar = whatever3(myFoo)(myBar)
sameBar2: myFoo.Bar = $anon$1@522f63e7

But not the fourth or fifth:

scala> val sameBar4: myFoo.Bar = whatever4(myFoo)(myBar)
<console>:12: error: type mismatch;
 found   : myFoo.Bar
 required: _1.foo.Bar where val _1: whatever4
       val sameBar4: myFoo.Bar = whatever4(myFoo)(myBar)
                                                  ^

Fair enough—we can't do the following, either, probably for similar reasons:

scala> val myOof = myFoo
myOof: Foo = $anon$1@39e4ff0c

scala> val myOofBar: myOof.Bar = new myFoo.Bar {}
<console>:10: error: type mismatch;
 found   : myFoo.Bar
 required: myOof.Bar
       val myOofBar: myOof.Bar = new myFoo.Bar {}
                                 ^

And it's not a big deal, anyway, since we've got three working solutions.

The problem

(I'll start by noting that while I first ran into the problem below while working with macros, and while my example here involves the reflection API, my question is not specific to macros or reflection.)

Suppose that I'm working with the new reflection API and want to be able to write the following:

applier[List[_]](Literal(Constant(42)), Literal(Constant(13)))

And have it mean something like "give me the abstract syntax tree for List(42, 13)". This isn't too hard—I can just use the approach from whatever3 above:

trait ReflectionUtils {
  import scala.reflect.api.Universe
 
  def companionApplier(u: Universe) = new {
    def apply[A: u.TypeTag](xs: u.Tree*): u.Tree = u.Apply(
      u.Select(u.Ident(u.typeOf[A].typeSymbol.companionSymbol), "apply"),
      xs.toList
    )
  }
}

And now I get the syntax I want in my macros (see my answer to this question for a more detailed and motivated example):

object MacroExample extends ReflectionUtils {
  import scala.language.experimental.macros
  import scala.language.reflectiveCalls
  import scala.reflect.macros.Context

  def threeOfThem(n: Int) = macro threeOfThem_impl
  def threeOfThem_impl(c: Context)(n: c.Expr[Int]) = {
    val applier = companionApplier(c.universe)

    c.Expr[List[Int]](applier[List[_]](n.tree, n.tree, n.tree))
  }
}

And everything works as intended. I don't really like the "reflective access of structural type member" business, though. Unfortunately I can't use the whatever1 or whatever2 approaches here, since I can't fix the type parameter when I apply this thing to my universe. I'd love to be able to write the following:

case class companionApplier(u: Universe) {
  def apply[A: u.TypeTag](xs: u.Tree*): u.Tree = u.Apply(
    u.Select(u.Ident(u.typeOf[A].typeSymbol.companionSymbol), "apply"),
    xs.toList
  )
}

But this of course runs me into the type mismatch problems we saw with whatever4 above.

Is there some other trick I'm missing? Is it possible for me to get the syntax I want without using an anonymous class with a structural type member?

like image 662
Travis Brown Avatar asked Mar 19 '13 20:03

Travis Brown


2 Answers

This should work:

case class companionApplier[U <: Universe](u: U) { ... }

// in macro
companionApplier[c.universe.type](c.universe)

I had a similar question some months ago, see here.

like image 100
kiritsuku Avatar answered Oct 23 '22 02:10

kiritsuku


How about splitting the structural type out as an auxiliary type and then sprinkling a little of my solution from the list,

scala> trait Foo { trait Bar }
defined trait Foo

scala> val myFoo = new Foo {} ; val myBar = new myFoo.Bar {}
myFoo: Foo = $anon$1@11247416
myBar: myFoo.Bar = $anon$2@70415924

scala> class Whatever6Aux[F <: Foo](val foo: F) { def apply(bar: foo.Bar) = bar }
defined class Whatever6Aux

scala> def whatever6(foo: Foo) = new Whatever6Aux[foo.type](foo)
whatever6: (foo: Foo)Whatever6Aux[foo.type]

scala> import scala.language.existentials
import scala.language.existentials

scala> whatever6(myFoo)(myBar)
res0: _1.foo.Bar forSome { val _1: Whatever6Aux[<refinement>.type] } = $anon$2@70415924
like image 28
Miles Sabin Avatar answered Oct 23 '22 00:10

Miles Sabin