dynamic混合性状
有一个特点
trait Persisted { def id: Long }
我该如何实现一个方法来接受任何case类的实例,并将其中的特性与混合的特性一起返回?
该方法的签名如下所示:
def toPersisted[T](instance: T, id: Long): T with Persisted
这可以用macros(从2.10.0-M3开始正式成为Scala的一部分)完成。 这里有一个你正在寻找的东西的例子 。
1)我的macros生成一个inheritance自提供的case类和Persisted的本地类,就像new T with Persisted
所做的new T with Persisted
一样。 然后它caching它的参数(防止多次评估)并创build创build的类的一个实例。
2)我怎么知道生成什么树? 我有一个简单的应用程序,parse.exe,打印parsinginput代码产生的AST。 所以我只是调用了parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persisted
,注意到输出并在macros中复制它。 parse.exe是scalac -Xprint:parser -Yshow-trees -Ystop-after:parser
的包装器。 有不同的方式来探索ASTs,阅读更多在“Scala 2.10中的元编程” 。
3)如果您提供-Ymacro-debug-lite
作为scalac的参数,则可以对macros扩展进行健全性检查。 在这种情况下,所有扩展都将被打印出来,并且您将能够更快地检测到codegen错误。
编辑。 更新了2.10.0-M7的示例
使用vanilla scala不可能实现你想要的。 问题是mixin如下所示:
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
创build一个Foo with Bar
混合,但它不是在运行时完成的。 编译器只是生成一个新的匿名类:
scala> fooWithBar.getClass res3: java.lang.Class[_ <: Foo] = class $anon$1
请参阅Scala中的dynamic混搭 – 是否有可能? 获取更多信息。
你所要做的就是所谓的logging串联,这是Scala的types系统不支持的。 (Fwiw,有types的系统 – 比如这个和这个 – 提供这个function。)
我认为types类可能适合你的用例,但我不能确定,因为这个问题没有提供你想要解决什么问题的足够信息。
更新
您可以find一个最新的工作解决scheme,它使用Scala 2.10.0-RC1的Toolboxes API作为SORM项目的一部分。
以下解决scheme基于Scala 2.10.0-M3reflectionAPI和Scala解释器。 它dynamic地创build和caching从混合了trait的原始案例类inheritance的类。由于最大限度的caching,这个解决scheme应该为每个原始案例类dynamic地创build一个类,并在以后重用。
由于新的reflectionAPI没有那么多的透露,也没有稳定的,没有教程,但这个解决scheme可能涉及一些愚蠢的行为和怪癖。
下面的代码是用Scala 2.10.0-M3进行testing的。
1. Persisted.scala
要注意的特性请注意,由于程序中的更新,我已经改变了一点
trait Persisted { def key: String }
2. PersistedEnabler.scala
实际的工人对象
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(".") } }
3. Sandbox.scala
testing应用程序
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) }
尽pipe在创build对象之后不可能创build对象,但是可以使用非常广泛的testing来确定对象是否使用types别名和定义结构的特定组合。
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()