我想要实现的是
的正确实施def dynamix[A, B](a: A): A with B
我可能知道B是什么,但不知道A是什么(但如果B有自我类型,那么我可以在A上添加一些约束)。 scala编译器对上面的签名很满意,但我还不知道实现的样子 - 如果可能的话。
我想到了一些选择:
你有其他想法可行吗?你会推荐哪种方式?期待什么样的“挑战”? 或者我应该忘记它,因为目前的Scala约束无法实现?
我的问题背后的意图:
说我有一个业务工作流程,但它不是太严格。有些步骤有固定的顺序,但有些步骤没有,但最后必须完成所有步骤(或者其中一些步骤需要进一步处理)。
更具体的例子:我有一个A,我可以添加B和C.我不在乎先做哪个,但最后我需要一个带B的A和C.
评论:我对Groovy不太了解,但是我突然出现了this question,我想这与我想要的大致相同,至少在概念上是这样。
答案 0 :(得分:26)
我认为在运行时不可能严格执行,因为traits在编译时混合到新的Java类中。如果你匿名地将一个特征与现有的类混合,你可以看到,通过查看类文件并使用javap,scalac创建了一个匿名的,名称受损的类:
class Foo {
def bar = 5
}
trait Spam {
def eggs = 10
}
object Main {
def main(args: Array[String]) = {
println((new Foo with Spam).eggs)
}
}
scalac Mixin.scala; ls *.class
返回
Foo.class Main$.class Spam$class.class
Main$$anon$1.class Main.class Spam.class
javap Main\$\$anon\$1
返回时
Compiled from "mixin.scala"
public final class Main$$anon$1 extends Foo implements Spam{
public int eggs();
public Main$$anon$1();
}
如您所见,scalac创建了一个在运行时加载的新匿名类;可能这个匿名类中的方法eggs
创建了一个Spam$class
的实例,并在其上调用eggs
,但我不完全确定。
然而,我们可以在这里做一个非常黑客的伎俩:
import scala.tools.nsc._;
import scala.reflect.Manifest
object DynamicClassLoader {
private var id = 0
def uniqueId = synchronized { id += 1; "Klass" + id.toString }
}
class DynamicClassLoader extends
java.lang.ClassLoader(getClass.getClassLoader) {
def buildClass[T, V](implicit t: Manifest[T], v: Manifest[V]) = {
// Create a unique ID
val id = DynamicClassLoader.uniqueId
// what's the Scala code we need to generate this class?
val classDef = "class %s extends %s with %s".
format(id, t.toString, v.toString)
println(classDef)
// fire up a new Scala interpreter/compiler
val settings = new Settings(null)
val interpreter = new Interpreter(settings)
// define this class
interpreter.compileAndSaveRun("<anon>", classDef)
// get the bytecode for this new class
val bytes = interpreter.classLoader.getBytesForClass(id)
// define the bytecode using this classloader; cast it to what we expect
defineClass(id, bytes, 0, bytes.length).asInstanceOf[Class[T with V]]
}
}
val loader = new DynamicClassLoader
val instance = loader.buildClass[Foo, Spam].newInstance
instance.bar
// Int = 5
instance.eggs
// Int = 10
由于需要使用Scala编译器AFAIK,这可能接近您可以做到的最干净的解决方案。这很慢,但记忆可能会有很大帮助。
这种做法非常荒谬,骇人听闻,并且与语言无关。我想各种各样的怪异虫子都可以悄悄进入;使用Java比我更长的人警告说,乱搞乱码器会带来疯狂。
答案 1 :(得分:3)
我希望能够在Spring应用程序上下文中构建Scala bean,但我还希望能够指定要包含在构造bean中的mixin:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:scala="http://www.springframework.org/schema/scala"
xsi:schemaLocation=...>
<scala:bean class="org.cakesolutions.scala.services.UserService" >
<scala:with trait="org.cakesolutions.scala.services.Mixin1" />
<scala:with trait="org.cakesolutions.scala.services.Mixin2" />
<scala:property name="dependency" value="Injected" />
<scala:bean>
</beans>
难点在于Class.forName函数不允许我指定mixins。最后,我将上述hacky解决方案扩展到了Scala 2.9.1。所以,这里充满了血腥;包括Spring的一些内容。
class ScalaBeanFactory(private val beanType: Class[_ <: AnyRef],
private val mixinTypes: Seq[Class[_ <: AnyRef]]) {
val loader = new DynamicClassLoader
val clazz = loader.buildClass(beanType, mixinTypes)
def getTypedObject[T] = getObject.asInstanceOf[T]
def getObject = {
clazz.newInstance()
}
def getObjectType = null
def isSingleton = true
object DynamicClassLoader {
private var id = 0
def uniqueId = synchronized { id += 1; "Klass" + id.toString }
}
class DynamicClassLoader extends java.lang.ClassLoader(getClass.getClassLoader) {
def buildClass(t: Class[_ <: AnyRef], vs: Seq[Class[_ <: AnyRef]]) = {
val id = DynamicClassLoader.uniqueId
val classDef = new StringBuilder
classDef.append("class ").append(id)
classDef.append(" extends ").append(t.getCanonicalName)
vs.foreach(c => classDef.append(" with %s".format(c.getCanonicalName)))
val settings = new Settings(null)
settings.usejavacp.value = true
val interpreter = new IMain(settings)
interpreter.compileString(classDef.toString())
val r = interpreter.classLoader.getResourceAsStream(id)
val o = new ByteArrayOutputStream
val b = new Array[Byte](16384)
Stream.continually(r.read(b)).takeWhile(_ > 0).foreach(o.write(b, 0, _))
val bytes = o.toByteArray
defineClass(id, bytes, 0, bytes.length)
}
}
代码还不能处理带参数的构造函数,也不能从父类的构造函数中复制注释(应该这样做吗?)。但是,它为我们提供了一个很好的起点,可以在scala Spring命名空间中使用。当然,不要只听我的话,在Specs2规范中验证它:
class ScalaBeanFactorySpec extends Specification {
"getTypedObject mixes-in the specified traits" in {
val f1 = new ScalaBeanFactory(classOf[Cat],
Seq(classOf[Speaking], classOf[Eating]))
val c1 = f1.getTypedObject[Cat with Eating with Speaking]
c1.isInstanceOf[Cat with Eating with Speaking] must_==(true)
c1.speak // in trait Speaking
c1.eat // in trait Eating
c1.meow // in class Cat
}
}