有特质
trait Persisted {
def id: Long
}
如何实现一个接受任何case类实例的方法,并返回混合了trait的副本?
该方法的签名如下:
def toPersisted[T](instance: T, id: Long): T with Persisted
答案 0 :(得分:31)
这可以通过宏(自2.10.0-M3以来正式成为Scala的一部分)来完成。 Here's a gist example of what you are looking for
1)我的宏生成一个本地类,它继承自提供的case类和Persisted,就像new T with Persisted
一样。然后它缓存它的参数(以防止多次评估)并创建一个创建的类的实例。
2)我怎么知道要生成什么树?我有一个简单的应用程序,parse.exe打印解析输入代码产生的AST。所以我只是调用parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persisted
,注意输出并在我的宏中重现它。 parse.exe是scalac -Xprint:parser -Yshow-trees -Ystop-after:parser
的包装器。探索AST的方法有很多种,请在"Metaprogramming in Scala 2.10"中阅读更多内容。
3)如果您将-Ymacro-debug-lite
作为参数提供给scalac,则可以对宏扩展进行健全性检查。在这种情况下,所有扩展都将打印出来,您将能够更快地检测到codegen错误。
修改。更新了2.10.0-M7的示例
答案 1 :(得分:9)
使用vanilla scala无法实现您想要的效果。问题是mixins如下:
scala> class Foo
defined class Foo
scala> trait Bar
defined trait Bar
scala> val fooWithBar = new Foo with Bar
fooWithBar: Foo with Bar = $anon$1@10ef717
创建一个混合的Foo with Bar
,但它不是在运行时完成的。编译器只生成一个新的匿名类:
scala> fooWithBar.getClass
res3: java.lang.Class[_ <: Foo] = class $anon$1
有关详细信息,请参阅Dynamic mixin in Scala - is it possible?。
答案 2 :(得分:4)
您尝试做的事情被称为记录连接,这是Scala的类型系统不支持的。 (Fwiw,存在类型系统 - 例如this和this - 提供此功能。)
我认为类型类可能适合您的用例,但我无法确定,因为该问题无法提供有关您尝试解决的问题的充分信息。
答案 3 :(得分:4)
您可以找到最新的工作解决方案,该解决方案使用Scala 2.10.0-RC1的Toolboxes API作为SORM项目的一部分。
以下解决方案基于Scala 2.10.0-M3反射API和Scala Interpreter。它动态创建和缓存从原始案例类继承的类,其中混合了特征。由于最大缓存,此解决方案应该为每个原始案例类动态创建一个类,并在以后重用它。
由于新的反射API没有被公开,也没有稳定,也没有教程,但这个解决方案可能涉及一些愚蠢的重新定位行为和怪癖。
以下代码使用Scala 2.10.0-M3进行了测试。
要混入的特性。请注意,由于我的程序更新,我已经稍微改了一下
trait Persisted {
def key: String
}
实际的工作者对象
import tools.nsc.interpreter.IMain
import tools.nsc._
import reflect.mirror._
object PersistedEnabler {
def toPersisted[T <: AnyRef](instance: T, key: String)
(implicit instanceTag: TypeTag[T]): T with Persisted = {
val args = {
val valuesMap = propertyValuesMap(instance)
key ::
methodParams(constructors(instanceTag.tpe).head.typeSignature)
.map(_.name.decoded.trim)
.map(valuesMap(_))
}
persistedClass(instanceTag)
.getConstructors.head
.newInstance(args.asInstanceOf[List[Object]]: _*)
.asInstanceOf[T with Persisted]
}
private val persistedClassCache =
collection.mutable.Map[TypeTag[_], Class[_]]()
private def persistedClass[T](tag: TypeTag[T]): Class[T with Persisted] = {
if (persistedClassCache.contains(tag))
persistedClassCache(tag).asInstanceOf[Class[T with Persisted]]
else {
val name = generateName()
val code = {
val sourceParams =
methodParams(constructors(tag.tpe).head.typeSignature)
val newParamsList = {
def paramDeclaration(s: Symbol): String =
s.name.decoded + ": " + s.typeSignature.toString
"val key: String" :: sourceParams.map(paramDeclaration) mkString ", "
}
val sourceParamsList =
sourceParams.map(_.name.decoded).mkString(", ")
val copyMethodParamsList =
sourceParams.map(s => s.name.decoded + ": " + s.typeSignature.toString + " = " + s.name.decoded).mkString(", ")
val copyInstantiationParamsList =
"key" :: sourceParams.map(_.name.decoded) mkString ", "
"""
class """ + name + """(""" + newParamsList + """)
extends """ + tag.sym.fullName + """(""" + sourceParamsList + """)
with """ + typeTag[Persisted].sym.fullName + """ {
override def copy(""" + copyMethodParamsList + """) =
new """ + name + """(""" + copyInstantiationParamsList + """)
}
"""
}
interpreter.compileString(code)
val c =
interpreter.classLoader.findClass(name)
.asInstanceOf[Class[T with Persisted]]
interpreter.reset()
persistedClassCache(tag) = c
c
}
}
private lazy val interpreter = {
val settings = new Settings()
settings.usejavacp.value = true
new IMain(settings, new NewLinePrintWriter(new ConsoleWriter, true))
}
private var generateNameCounter = 0l
private def generateName() = synchronized {
generateNameCounter += 1
"PersistedAnonymous" + generateNameCounter.toString
}
// REFLECTION HELPERS
private def propertyNames(t: Type) =
t.members.filter(m => !m.isMethod && m.isTerm).map(_.name.decoded.trim)
private def propertyValuesMap[T <: AnyRef](instance: T) = {
val t = typeOfInstance(instance)
propertyNames(t)
.map(n => n -> invoke(instance, t.member(newTermName(n)))())
.toMap
}
private type MethodType = {def params: List[Symbol]; def resultType: Type}
private def methodParams(t: Type): List[Symbol] =
t.asInstanceOf[MethodType].params
private def methodResultType(t: Type): Type =
t.asInstanceOf[MethodType].resultType
private def constructors(t: Type): Iterable[Symbol] =
t.members.filter(_.kind == "constructor")
private def fullyQualifiedName(s: Symbol): String = {
def symbolsTree(s: Symbol): List[Symbol] =
if (s.enclosingTopLevelClass != s)
s :: symbolsTree(s.enclosingTopLevelClass)
else if (s.enclosingPackageClass != s)
s :: symbolsTree(s.enclosingPackageClass)
else
Nil
symbolsTree(s)
.reverseMap(_.name.decoded)
.drop(1)
.mkString(".")
}
}
测试应用
import PersistedEnabler._
object Sandbox extends App {
case class Artist(name: String, genres: Set[Genre])
case class Genre(name: String)
val artist = Artist("Nirvana", Set(Genre("rock"), Genre("grunge")))
val persisted = toPersisted(artist, "some-key")
assert(persisted.isInstanceOf[Persisted])
assert(persisted.isInstanceOf[Artist])
assert(persisted.key == "some-key")
assert(persisted.name == "Nirvana")
assert(persisted == artist) // an interesting and useful effect
val copy = persisted.copy(name = "Puddle of Mudd")
assert(copy.isInstanceOf[Persisted])
assert(copy.isInstanceOf[Artist])
// the only problem: compiler thinks that `copy` does not implement `Persisted`, so to access `key` we have to specify it manually:
assert(copy.asInstanceOf[Artist with Persisted].key == "some-key")
assert(copy.name == "Puddle of Mudd")
assert(copy != persisted)
}
答案 4 :(得分:-1)
虽然在创建对象后无法编写对象,但您可以进行非常广泛的测试,以使用类型别名和定义结构来确定对象是否具有特定的组合:
type Persisted = { def id: Long }
class Person {
def id: Long = 5
def name = "dude"
}
def persist(obj: Persisted) = {
obj.id
}
persist(new Person)
任何带有def id:Long
的对象都有资格成为Persisted。
通过隐式转换实现我想要做的事情:
object Persistable {
type Compatible = { def id: Long }
implicit def obj2persistable(obj: Compatible) = new Persistable(obj)
}
class Persistable(val obj: Persistable.Compatible) {
def persist() = println("Persisting: " + obj.id)
}
import Persistable.obj2persistable
new Person().persist()