Có những trường hợp sử dụng hữu ích khi tạo bản sao của một đối tượng là một thể hiện của một lớp chữ của một tập hợp các lớp chữ hoa, có một giá trị cụ thể chung.Mô hình howto được đặt tên tham số trong lời gọi phương thức với macro Scala?
Ví dụ chúng ta hãy xem xét các lớp trường hợp sau đây:
case class Foo(id: Option[Int])
case class Bar(arg0: String, id: Option[Int])
case class Baz(arg0: Int, id: Option[Int], arg2: String)
Sau đó copy
có thể được gọi vào mỗi trường hợp này trường hợp lớp:
val newId = Some(1)
Foo(None).copy(id = newId)
Bar("bar", None).copy(id = newId)
Baz(42, None, "baz").copy(id = newId)
Như đã trình bày here và here không có cách nào đơn giản để trừu tượng điều này như sau:
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)
Vì vậy, tôi đã tạo ra một vĩ mô scala, mà làm công việc này (hầu như):
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(..., id: Option[Int], ...): " + currentType + "'")
}
}
}
Đối số cuối cùng của Apply
(xem dưới cùng của khối mã ở trên) là một danh sách các thông số (ở đây: các tham số của phương pháp 'bản sao'). Làm cách nào để có thể nhập id
loại c.Expr[Option[Int]]
như tham số được đặt tên cho phương thức sao chép với sự trợ giúp của API macro mới?
Đặc biệt khái niệm vĩ mô sau
c.Expr(
Apply(
Select(
reify(entity.splice).tree,
newTermName("copy")),
List(/*?id?*/)))
nên kết quả trong
entity.copy(id = id)
để sau giữ
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)))
}
Phần thiếu được ký hiệu bởi các placeholder /*?id?*/
.
Cảm ơn bạn, tôi thích conciseness của giải pháp này.Nó áp dụng tốt cho trường hợp sử dụng của tôi. Có lẽ phần s.paramss.head cần kiểm tra bổ sung cho các phương thức nullary (= các phương thức không có danh sách đối số), tức là khi s.paramss trả về List()/Nil. Nhưng kết quả là như nhau: macro không thể được áp dụng. –
@DanielDietrich: Điểm tốt, và tôi đã thêm kiểm tra đó, nhưng lưu ý rằng đây chỉ là một bản phác thảo và vẫn còn ít nhất một giả định tương tự trong phiên bản sửa đổi (chỉ có một phương thức có tên là 'copy'). May mắn thay điều tồi tệ nhất có thể xảy ra là một lỗi biên dịch hơi khó hiểu. –
Vâng, bạn nói đúng. Như bạn đã nói trong bài viết đầu tiên của bạn, một kiểm tra cho sự tồn tại của id param có thể được thực hiện. Trong giải pháp hiện tại đã có lỗi biên dịch, nếu thiếu id thông số. Để nhận được thông báo lỗi trình biên dịch chi tiết hơn, tôi sẽ thay đổi if-guard của mẫu khớp với (s.paramss.flatten.map (_. Name) .contains (newTermName ("id"))). Với điều này, phương pháp nullary cũng bị bắt. –