scala 动态混合一个特征
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10373318/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mixing in a trait dynamically
提问by Nikita Volkov
Having a trait
有特质
trait Persisted {
def id: Long
}
how do I implement a method that accepts an instance of any case class and returns its copy with the trait mixed in?
如何实现一个方法,该方法接受任何 case 类的实例并返回其混合了特征的副本?
The signature of the method looks like:
该方法的签名如下所示:
def toPersisted[T](instance: T, id: Long): T with Persisted
采纳答案by Eugene Burmako
This can be done with macros (that are officially a part of Scala since 2.10.0-M3). Here's a gist example of what you are looking for.
这可以通过宏来完成(从 2.10.0-M3 开始正式成为 Scala 的一部分)。这是您正在寻找的内容的要点示例。
1) My macro generates a local class that inherits from the provided case class and Persisted, much like new T with Persistedwould do. Then it caches its argument (to prevent multiple evaluations) and creates an instance of the created class.
1) 我的宏生成一个从提供的案例类和 Persisted 继承的本地类,就像new T with Persisted会做的那样。然后它缓存它的参数(以防止多次评估)并创建所创建类的实例。
2) How did I know what trees to generate? I have a simple app, parse.exe that prints the AST that results from parsing input code. So I just invoked parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persisted, noted the output and reproduced it in my macro. parse.exe is a wrapper for scalac -Xprint:parser -Yshow-trees -Ystop-after:parser. There are different ways to explore ASTs, read more in "Metaprogramming in Scala 2.10".
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 的方法有很多种,请阅读“Scala 2.10 中的元编程”。
3) Macro expansions can be sanity-checked if you provide -Ymacro-debug-liteas an argument to scalac. In that case all expansions will be printed out, and you'll be able to detect codegen errors faster.
3) 如果您-Ymacro-debug-lite作为 scalac 的参数提供,则可以对宏扩展进行完整性检查。在这种情况下,所有扩展都将被打印出来,您将能够更快地检测代码生成错误。
edit. Updated the example for 2.10.0-M7
编辑。更新了 2.10.0-M7 的示例
回答by Emil H
It is not possible to achieve what you want using vanilla scala. The problem is that the mixins like the following:
使用 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@10ef717
create a Foo with Barmixed in, but it is not done at runtime. The compiler simply generates a new anonymous class:
创建一个Foo with Bar混合,但它不是在运行时完成的。编译器只是生成一个新的匿名类:
scala> fooWithBar.getClass
res3: java.lang.Class[_ <: Foo] = class $anon
See Dynamic mixin in Scala - is it possible?for more info.
参见Scala 中的动态混合 - 有可能吗?了解更多信息。
回答by Nikita Volkov
Update
更新
You can find an up to date working solution, which utilizes a Toolboxes API of Scala 2.10.0-RC1 as part of SORMproject.
您可以找到一个最新的工作解决方案,它使用 Scala 2.10.0-RC1 的 Toolboxes API 作为SORM项目的一部分。
The following solution is based on the Scala 2.10.0-M3 reflection API and Scala Interpreter. It dynamically creates and caches classes inheriting from the original case classes with the trait mixed in. Thanks to caching at maximum this solution should dynamically create only one class for each original case class and reuse it later.
以下解决方案基于 Scala 2.10.0-M3 反射 API 和 Scala Interpreter。它动态地创建和缓存从原始案例类继承的类,并混合了特征。由于最大缓存,该解决方案应该为每个原始案例类动态创建一个类,并在以后重用它。
Since the new reflection API isn't that much disclosed nor is it stable and there are no tutorials on it yet this solution may involve some stupid repitative actions and quirks.
由于新的反射 API 没有公开太多,也不是很稳定,也没有关于它的教程,但是这个解决方案可能涉及一些愚蠢的重复操作和怪癖。
The following code was tested with Scala 2.10.0-M3.
以下代码使用 Scala 2.10.0-M3 进行了测试。
1. Persisted.scala
1.持久化.scala
The trait to be mixed in. Please note that I've changed it a bit due to updates in my program
要混合的特征。请注意,由于我的程序更新,我对其进行了一些更改
trait Persisted {
def key: String
}
2. PersistedEnabler.scala
2. PersistedEnabler.scala
The actual worker object
实际的工作对象
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
3. 沙盒.scala
The test app
测试应用
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)
}
回答by missingfaktor
What you are trying to do is known as record concatenation, something that Scala's type system does not support. (Fwiw, there exist type systems - such as thisand this- that provide this feature.)
您尝试做的是记录串联,Scala 的类型系统不支持这种做法。(Fwiw,存在类型系统 - 例如这个和这个- 提供这个功能。)
I think type classes might fit your use case, but I cannot tell for sure as the question doesn't provide sufficient information on what problem you are trying to solve.
我认为类型类可能适合您的用例,但我不能确定,因为该问题没有提供有关您要解决的问题的足够信息。
回答by Nthalk
While it's not possible to compose an object AFTER it's creation, you can have very wide tests to determine if the object is of a specific composition using type aliases and definition structs:
虽然不可能在创建后组合对象,但您可以进行非常广泛的测试来确定对象是否具有使用类型别名和定义结构的特定组合:
type Persisted = { def id: Long }
class Person {
def id: Long = 5
def name = "dude"
}
def persist(obj: Persisted) = {
obj.id
}
persist(new Person)
Any object with a def id:Longwill qualify as Persisted.
任何带有 的对象def id:Long都将被视为持久化对象。
Achieving what I THINK you are trying to do is possible with implicit conversions:
通过隐式转换可以实现我认为您正在尝试做的事情:
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()

