Future-proof macros?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Future-proof macros?

Alan Burlison
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
--

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Future-proof macros?

Naftoli Gugenheim
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?

On Wed, Apr 26, 2017 at 11:54 AM Alan Burlison <[hidden email]> wrote:
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
--

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: deferring generic type resolution (was Future-proof macros?)

Alan Burlison
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
--

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: deferring generic type resolution (was Future-proof macros?)

Rodrigo Cano
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.

On Fri, Apr 28, 2017 at 10:43 AM, Alan Burlison <[hidden email]> wrote:
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
--

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: deferring generic type resolution (was Future-proof macros?)

Alan Burlison
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
--

--
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 [hidden email].
For more options, visit https://groups.google.com/d/optout.