2

I wonder if this can be done with scala macros

Consider this method:

def doSomething(filter: Item => Boolean) = ...

class Item(properties: Map[String, String]) extends Dynamic {
  def selectDynamic(name:String): String = {
    properties.getOrElse(name, "")
  }
  def updateDynamic(name: String)(value: String): Unit = {
    addProperty(name, value)
  }
}

And this usage

doSomething(x=> x.foo == "X" && x.bar  == "Y" || x.baz.nonEmpty)

What I'd like to do is to simplify it (I'm writing a DSL for people who don't really use scala much) to this:

doSomething(foo == "X" && bar  == "Y" || baz.nonEmpty)

My assumption is that even with Scala macros this might be impossible, or is it?

If so, where do I start? I assume this is not a simplistic macro...

Eran Medan
  • 44,555
  • 61
  • 184
  • 276
  • 3
    If `doSomething` is a macro, it should be possible to enable this syntax. The macro could transform `doSomething(foo == "X")` to `doSomething{x => import x._; foo == "X"}` – kiritsuku Mar 11 '15 at 00:26
  • Ok, I'll try to dig in. Is there a good up to date resource on macros I can use? – Eran Medan Mar 11 '15 at 00:30
  • Hm... I don't think it's possible, I need to match an unknown structure that can be recursive (is there a way to match an "arbitrary boolean expression that can't really compile?") – Eran Medan Mar 11 '15 at 01:35
  • Ok, one direction is via symbols... and since 2.11 you can use by name parameters to macros. So it might be doable but not sure if worth the effort :) – Eran Medan Mar 11 '15 at 02:37
  • @sschaef - I tried, it seems it wants the identifier to be valid first before expanding the macro, I wish I could tell scala to first expand the macro and only then try to figure out what is that identifier (e.g. `foo`). p.s. even without macros, dynamic calls only work with an explicit dot sadly. e.g. just writing this in the REPL (without any macro): `x = new Item(..); import x._ ; blah` doesn't work, you have to explicitly use . on x: `x = new Item(..); x.blah` – Eran Medan Mar 11 '15 at 05:10
  • 1
    Yes, in order for `doSomething(foo == "X")` to expand, `foo == "X"` needs to be type-correct in the first place. – Eugene Burmako Mar 11 '15 at 08:42

1 Answers1

3

You're right that the identifiers must be introduced.

In his ugliest code and worst pun on Metaplasm, Travis Brown called it "Potemkin val-age". A macro introduces the syntax machinery, which is imported before use (or abuse).

Potemkin definitions may not suit your use case if your property names are just arbitrary data.

Maybe something like this to collect the syntax, then a macro could operate on it (as opposed to the quickie conversion here).

case object is

object when extends Dynamic {
  def applyDynamic(f: String)(op: Any) = new P(f, op)
}

case class P(f: String, op: Any) extends Dynamic {
  def applyDynamic(op: String)(value: String) = Q(this, op, value)
}
case class Q(p: P, op: String, value: String)

object Test extends App {
  implicit def `Q to filter`(q: Q): Item => Boolean = (i: Item) => (q.p.op, q.op) match {
    case (is, "equal") => i.properties.get(q.p.f) map (_ == q.value) getOrElse false
    case _  => false
  }
  val items = List (
    Item(Map("foo" -> "X", "bar" -> "Y")),
    Item(Map("this" -> "that")),
    Item(Map("baz" -> "more"))
  )
  def doSomething(filter: Item => Boolean) = Console println (items filter filter)

  doSomething(when foo is equal "X")
}

I didn't give Dynamic a thought until I saw this earlier today: https://stackoverflow.com/a/16256935/1296806

Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129