Future-proof macros?

62 views
Skip to first unread message

Alan Burlison

unread,
Apr 26, 2017, 11:54:38 AM4/26/17
to scala-user
I need to replace some simple boilerplate - nothing that would tax even
a C preprocessor - but it's unclear what variant of Scala's macro
support I should use. Much of the macro information on scala-lang.org
appears to be out of date and/or contradictory. What should I be using
for a simple macro use case that will be relatively future-proof?

Thanks,

--
Alan Burlison
--

Naftoli Gugenheim

unread,
Apr 28, 2017, 4:59:10 AM4/28/17
to Alan Burlison, scala-user
Not sure but I think quasiquotes are the safest.

Are you sure it needs a macro? Do you want to describe in more detail what your ultimate goal is?

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alan Burlison

unread,
Apr 28, 2017, 9:43:28 AM4/28/17
to Naftoli Gugenheim, scala-user
On 28/04/17 09:58, Naftoli Gugenheim wrote:

> Not sure but I think quasiquotes are the safest.
>
> Are you sure it needs a macro? Do you want to describe in more detail what
> your ultimate goal is?

I think you may be right - I tried to figure out quasiquotes and after
reading some examples and then spending an age trying and failing to
find out what ".." and "..." did, I gave up. I also had a look at
scala.meta which I understand is supposed to replace the current macro
system, but it appears you need a build.sbt that's longer than the
program itself just to get it to compile...

What I'm trying to do is a variant on the hoary old "update a case class
from a map" problem. The wrinkles are that the map in question is an
akka-http-spray-json JsObject, namely a Map[String, JsValue] and the
JsObject fields and corresponding case class fields may have different
names.

If the JsObject contains a key corresponding to a case class field, I
want to copy the case class instance with the relevant field updated
with the value of the corresponding JsObject entry, otherwise I want the
unmodified case class instance back. Here's what I have so far:

def makeUpdater[C, T](obj: C, params: JsObject)(updater: (C, T) => C,
key: String)
(implicit jr: JsonReader[T]): C = {
params.fields.get(key) match {
case Some(v) => updater(obj, v.convertTo[T])
case None => obj
}
}

Note that has three argument lists, the last one is to pick up an
implicit JsonReader for doing the convertTo call, the intent of the
first and second are so that you can do something along the lines of:

val json: JsObject = ...
var props = ... // Some case class
def update = makeUpdater(props, json) _
props = update((p, v) => p.copy(loginName = v), "login_name")
props = update((p, v) => p.copy(password = v), "passwd")

except that doesn't actually work :-(

Error:(210, 29) Cannot find JsonReader or JsonFormat type class for Nothing
def update = makeUpdater(props, json) _

I believe that's because the compiler can't resolve the type of T, and
therefore it also can't find the correct implicit JsonReader.

This on the other hand *does* work:

props = makeUpdater(props, json)((p, v) => p.copy(loginName = v),
"login_name")

What I want is for partial application of makeUpdater to preserve the
generic nature of T, but I can't figure out if that's possible or not.
Something along the lines of:

def makeUpdater[C, T](obj: C, params: JsObject)(updater[T]: (C, T) => C,
key: String)

but of course that's syntactically invalid. I suspect there may be a way
of doing this, I just can't figure out what it is.

The original reason of looking at macros for a solution is because the
operation itself seemed simple - if the JsObject map contains the key,
get the value, convert it with the corresponding JsReader and pass to
the case class object's copy method. If it doesn't, return the original
case class instance.

--
Alan Burlison
--

Rodrigo Cano

unread,
Apr 28, 2017, 1:09:45 PM4/28/17
to Alan Burlison, Naftoli Gugenheim, scala-user
Hi,

The nature of the problem stems from the fact that functions can't have implicit parameter lists, so when you partially apply it, the compiler is forced to resolve the implicit, and since you second parameter is not present it resolves to nothing.

However, there is a trick you can pull to have a sort of two staged type resolution for you partially applied method, using an intermediate class with an apply method. Something along the lines of

def makeUpdater[C](obj: C, params: JsObject) = new Updater(obj, params)
class Updater[C](obj: C, params: JsObject) {
  def apply[T](updater: (C, T) => C, key: String)(implicit jr: JsonReader[T]): C = {

  params.fields.get(key) match {
    case Some(v) => updater(obj, v.convertTo[T])
    case None => obj
  }
}

Usage would then be:


val json: JsObject = ...
var props = ... // Some case class
def update = makeUpdater(props, json)
props = update((p, v) => p.copy(loginName = v), "login_name")
props = update((p, v) => p.copy(password = v), "passwd")

Cheers.



--
Alan Burlison
--

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Alan Burlison

unread,
Apr 28, 2017, 3:08:54 PM4/28/17
to Rodrigo Cano, Naftoli Gugenheim, scala-user
On 28/04/17 18:09, Rodrigo Cano wrote:

> The nature of the problem stems from the fact that functions can't have
> implicit parameter lists, so when you partially apply it, the compiler is
> forced to resolve the implicit, and since you second parameter is not
> present it resolves to nothing.

Ah, that makes sense, thanks for the explanation.

> However, there is a trick you can pull to have a sort of two staged type
> resolution for you partially applied method, using an intermediate class
> with an apply method. Something along the lines of
>
> def makeUpdater[C](obj: C, params: JsObject) = new Updater(obj, params)
> class Updater[C](obj: C, params: JsObject) {
> def apply[T](updater: (C, T) => C, key: String)(implicit jr:
> JsonReader[T]): C = {
> params.fields.get(key) match {
> case Some(v) => updater(obj, v.convertTo[T])
> case None => obj
> }
> }

Oh, that's a neat trick that I'd never have thought of :-)

> Usage would then be:
>
> val json: JsObject = ...
> var props = ... // Some case class
> def update = makeUpdater(props, json)
> props = update((p, v) => p.copy(loginName = v), "login_name")
> props = update((p, v) => p.copy(password = v), "passwd")

Only one small tweak needed, the addition of the type of the case class
field being assigned to:

props = update[String]((p, v) => p.copy(loginName = v), "login_name")

I'd have thought the compiler could have inferenced that, but apparently
not.

Thanks for the help, much appreciated :-)

--
Alan Burlison
--
Reply all
Reply to author
Forward
0 new messages