加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

在Scala宏的方法调用中如何模型命名参数?

发布时间:2020-12-16 09:44:17 所属栏目:安全 来源:网络整理
导读:存在用于创建对象副本的用例,该对象是一组具有特定值的案例类的案例类的实例。 例如,让我们考虑以下案例类: case class Foo(id: Option[Int])case class Bar(arg0: String,id: Option[Int])case class Baz(arg0: Int,id: Option[Int],arg2: String) 然后
存在用于创建对象副本的用例,该对象是一组具有特定值的案例类的案例类的实例。

例如,让我们考虑以下案例类:

case class Foo(id: Option[Int])
case class Bar(arg0: String,id: Option[Int])
case class Baz(arg0: Int,id: Option[Int],arg2: String)

然后可以在每个这些case类实例上调用copy:

val newId = Some(1)

Foo(None).copy(id = newId)
Bar("bar",None).copy(id = newId)
Baz(42,None,"baz").copy(id = newId)

如here和here所述,没有简单的方法来抽象这样:

type Copyable[T] = { def copy(id: Option[Int]): T }

// THIS DOES *NOT* WORK FOR CASE CLASSES
def withId[T <: Copyable[T]](obj: T,newId: Option[Int]): T =
  obj.copy(id = newId)

所以我创建了一个scala宏,它做这个工作(几乎):

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) => `type` == returnType
    }
    def hasParameter(name: String,`type`: Type)(implicit method: MethodSymbol) = method.typeSignature match {
      case MethodType(params,_) => params.exists { param =>
        equals(param.name,name) && param.typeSignature == `type`
      }
    }

    // finding method entity.copy(id: Option[Int])
    currentType.members.find { symbol =>
      symbol.isMethod && {
        implicit val method = symbol.asMethod
        hasName("copy") && hasReturnType(currentType) && hasParameter("id",typeOf[Option[Int]])
      }
    } match {
      case Some(symbol) => {
        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 => c.abort(c.enclosingPosition,currentType + " needs method 'copy(...,...): " + currentType + "'")
    }

  }

}

Apply的最后一个参数(见上面代码块的底部)是一个参数列表(这里是“copy”方法的参数)。在新的宏API的帮助下,如何将类型为c.Expr [Option [Int])的给定id作为命名参数传递给复制方法?

特别是下面的宏表达式

c.Expr(
  Apply(
    Select(
      reify(entity.splice).tree,List(/*?id?*/)))

应该导致

entity.copy(id = id)

以致如下

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)))

}

缺少的部分由占位符/ *?id?* /表示。

解决方法

这是一个更通用的实现:

import scala.language.experimental.macros

object WithIdExample {
  import scala.reflect.macros.Context

  def withId[T,I](entity: T,id: I): T = macro withIdImpl[T,I]

  def withIdImpl[T: c.WeakTypeTag,I: c.WeakTypeTag](c: Context)(
    entity: c.Expr[T],id: c.Expr[I]
  ): c.Expr[T] = {
    import c.universe._

    val tree = reify(entity.splice).tree
    val copy = entity.actualType.member(newTermName("copy"))

    val params = copy match {
      case s: MethodSymbol if (s.paramss.nonEmpty) => s.paramss.head
      case _ => c.abort(c.enclosingPosition,"No eligible copy method!")
    }

    c.Expr[T](Apply(
      Select(tree,copy),params.map {
        case p if p.name.decoded == "id" => reify(id.splice).tree
        case p => Select(tree,p.name)
      }
    ))
  }
}

它将适用于任何具有名为id的成员的案例类,无论其类型如何:

scala> case class Bar(arg0: String,id: Option[Int])
defined class Bar

scala> case class Foo(x: Double,y: String,id: Int)
defined class Foo

scala> WithIdExample.withId(Bar("bar",None),Some(2))
res0: Bar = Bar(bar,Some(2))

scala> WithIdExample.withId(Foo(0.0,"foo",1),2)
res1: Foo = Foo(0.0,foo,2)

如果case类没有id成员,那么useId将被编译,它不会做任何事情。如果在这种情况下想要编译错误,可以为复制的匹配添加一个额外的条件。

编辑:正如尤金·布马科刚刚指出的那样,on Twitter,你可以使用AssignOrNamedArg自然地写一些这样的结尾:

c.Expr[T](Apply(
  Select(tree,AssignOrNamedArg(Ident("id"),reify(id.splice).tree) :: Nil
))

如果case类没有id成员,那么这个版本将不会被编译,但是更有可能是所需的行为。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读