Note that there are some explanatory texts on larger screens.

plurals
  1. POHowto model named parameters in method invocations with Scala macros?
    primarykey
    data
    text
    <p>There are use cases where it is useful to create a copy of an object which is an instance of a case class of a set of case classes, which have a specific value in common.</p> <p>For example let's consider the following case classes:</p> <pre><code>case class Foo(id: Option[Int]) case class Bar(arg0: String, id: Option[Int]) case class Baz(arg0: Int, id: Option[Int], arg2: String) </code></pre> <p>Then <code>copy</code> can be called on each of these case class instances:</p> <pre><code>val newId = Some(1) Foo(None).copy(id = newId) Bar("bar", None).copy(id = newId) Baz(42, None, "baz").copy(id = newId) </code></pre> <p>As described <a href="http://www.scala-lang.org/node/4620" rel="nofollow noreferrer">here</a> and <a href="https://stackoverflow.com/questions/12370244/case-class-copy-method-with-superclass">here</a> there is no simple way to abstract this like this:</p> <pre><code>type Copyable[T] = { def copy(id: Option[Int]): T } // THIS DOES *NOT* WORK FOR CASE CLASSES def withId[T &lt;: Copyable[T]](obj: T, newId: Option[Int]): T = obj.copy(id = newId) </code></pre> <p>So I created a scala macro, which does this job (almost):</p> <pre><code>import scala.reflect.macros.Context object Entity { import scala.language.experimental.macros import scala.reflect.macros.Context def withId[T](entity: T, id: Option[Int]): T = macro withIdImpl[T] def withIdImpl[T: c.WeakTypeTag](c: Context)(entity: c.Expr[T], id: c.Expr[Option[Int]]): c.Expr[T] = { import c.universe._ val currentType = entity.actualType // reflection helpers def equals(that: Name, name: String) = that.encoded == name || that.decoded == name def hasName(name: String)(implicit method: MethodSymbol) = equals(method.name, name) def hasReturnType(`type`: Type)(implicit method: MethodSymbol) = method.typeSignature match { case MethodType(_, returnType) =&gt; `type` == returnType } def hasParameter(name: String, `type`: Type)(implicit method: MethodSymbol) = method.typeSignature match { case MethodType(params, _) =&gt; params.exists { param =&gt; equals(param.name, name) &amp;&amp; param.typeSignature == `type` } } // finding method entity.copy(id: Option[Int]) currentType.members.find { symbol =&gt; symbol.isMethod &amp;&amp; { implicit val method = symbol.asMethod hasName("copy") &amp;&amp; hasReturnType(currentType) &amp;&amp; hasParameter("id", typeOf[Option[Int]]) } } match { case Some(symbol) =&gt; { val method = symbol.asMethod val param = reify(( c.Expr[String](Literal(Constant("id"))).splice, id.splice)).tree c.Expr( Apply( Select( reify(entity.splice).tree, newTermName("copy")), List( /*id.tree*/ ))) } case None =&gt; c.abort(c.enclosingPosition, currentType + " needs method 'copy(..., id: Option[Int], ...): " + currentType + "'") } } } </code></pre> <p>The last argument of <code>Apply</code> (see bottom of above code block) is a List of parameters (here: parameters of method 'copy'). How can the given <code>id</code> of type <code>c.Expr[Option[Int]]</code> be passed as named parameter to the copy method with the help of the new macro API?</p> <p>In particular the following macro expression</p> <pre><code>c.Expr( Apply( Select( reify(entity.splice).tree, newTermName("copy")), List(/*?id?*/))) </code></pre> <p>should result in</p> <pre><code>entity.copy(id = id) </code></pre> <p>so that the following holds</p> <pre><code>case class Test(s: String, id: Option[Int] = None) // has to be compiled by its own object Test extends App { assert( Entity.withId(Test("scala rulz"), Some(1)) == Test("scala rulz", Some(1))) } </code></pre> <p>The missing part is denoted by the placeholder <code>/*?id?*/</code>.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload