6

Update: I suspect that what I want might not be possible, and I've written up a blog post with my reasoning (and some alternatives) here. I'd be very happy to be told that I'm wrong.


Suppose I want to create a instance of a trait using a factory method with a macro implementation. This method will take as an argument a path to a resource that the macro will read and parse (at compile time) into a map from strings to strings.

That part is all fairly straightforward. Now suppose I want to associate the resulting map with the instance I'm creating so that I can use it in subsequent macro calls involving that instance.

I crucially do not want the map to be a member of the instance, or to exist in any form at runtime. I also don't want to parse the same resource more than once. Here's a sketch of the kind of thing I'm aiming for:

import scala.language.experimental.macros
import scala.reflect.macros.Context

trait Foo {
  def lookup(key: String): String = macro Foo.lookup_impl
}

object Foo {
  def parseResource(path: String): Map[String, String] = ???

  def apply(path: String): Foo = macro apply_impl

  def apply_impl(c: Context)(path: c.Expr[String]): c.Expr[Foo] = {
    import c.universe._

    val m = path.tree match {
      case Literal(Constant(s: String)) => parseResource(s)
    }

    val tree = reify(new Foo {}).tree
    // Somehow associate the map with this tree here.
    c.Expr(tree)
  }

  def lookup_impl(c: Context)(key: c.Expr[String]): c.Expr[String] =
    /* Somehow read off the map and look up this key. */ ???
}

This seems to be the sort of thing that attachments are designed to help with (thanks to Eugene Burmako for the pointer), and I've got an attachment-based implementation that allows me to write the following:

Foo("whatever.txt").lookup("x")

Where apply_impl attaches the map to the tree and lookup_impl reads that attachment off the same tree, which it sees as its prefix. Unfortunately this is more or less useless, though, since it doesn't work in the foo.lookup("x") case, where the prefix tree is just the variable foo.

(Note that in my real use case Foo extends Dynamic, and I'm trying to give a macro implementation for selectDynamic instead of lookup, but that shouldn't be relevant here—I'm interested in the general case.)

Is there some way that I can use attachments to get what I want? Is there some other approach that would be more appropriate?

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 2
    Suppose I want to follow the Qs and As only of people who ask and answer interesting questions. Maybe I can filter twitter for that, but is there a Stack App? Maybe it's time to write one. I think I previously saw that the name "Stalk Exchange" was already taken. "Sticker"? – som-snytt Jul 10 '13 at 23:28
  • Hope that means I make the cut, @som-snytt! – Travis Brown Jul 10 '13 at 23:37
  • If the usage of type macros is acceptable, on could imagine `Foo` to have a generated type member that encapsulates the `Map`. The path-dependent type system should then correctly propagate the inner types. **EDIT** Type macros could also just generate a proper class in the first place. No dynamic magic required. – gzm0 Jul 11 '13 at 01:12
  • @gzm0: Not sure I understand. You could [give the instance a type member](http://stackoverflow.com/q/14370842/334519) without type macros. In fact you can [accomplish the big-picture version of I'm trying to do here](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/) without attachments, by using structural types instead of `Dynamic`—I just wanted to try a version that would avoid reflective access. – Travis Brown Jul 11 '13 at 01:19
  • @TravisBrown The idea I initially had is to give `Foo` an abstract type member and upon creation, create an anonymous class representing the map somehow (`HLists` + your literal singleton types?). Then, when calling lookup and the type member is preserved, you can use the type itself to reconstruct the map. Since all this is generic, it is going to be erased in the JVM. – gzm0 Jul 11 '13 at 01:33
  • @gzm0: See [this similar proposal by Miles Sabin](https://twitter.com/milessabin/status/355073188893433857), where the idea is to stick the path's singleton type in a type member and maintain a separate map (at the value level, but compile-time only) from path strings to maps. It's interesting, but I'm still holding out hope for a simpler attachment-based approach. – Travis Brown Jul 11 '13 at 01:54

1 Answers1

0

UPDATE I should read the question again after fiddling... :( I just reproduced what you have.


You were quite there I think. This works for me:

object Foo {

  // Attachment.get somehow needs a wrapper class.
  // Probably because of generics
  implicit class LookupMap(m: Map[String, String]) {
    def get(s: String) = m.get(s) 
  }

  def parseResource(path: String): LookupMap = Map("test" -> "a")

  def apply_impl(c: Context)(path: c.Expr[String]): c.Expr[Foo] = {
    import c.universe._

    val m = path.tree match {
      case Literal(Constant(s: String)) => parseResource(s)
    }

    val tree = reify(new Foo {}).tree.updateAttachment(m)

    c.Expr(tree)
  }

  def lookup_impl(c: Context { type PrefixType = Foo })
                 (key: c.Expr[String]): c.Expr[String] = {
    import c.universe._

    val m = c.prefix.tree.attachments.get[LookupMap].get

    val s = key.tree match {
      case Literal(Constant(s: String)) => m.get(s).get
    }

    c.Expr(Literal(Constant(s)))

  }

}
gzm0
  • 14,752
  • 1
  • 36
  • 64
  • Yep, this is just about exactly what I had. The wrapper for the map is necessary because the attachments are indexed by the erased class of the type. – Travis Brown Jul 11 '13 at 00:57