What I'd like to achieve is having a proper implementation for
def dynamix[A, B](a: A): A with B
I may know what B is, but don't know what A is (but if B has a self type then I could add some constraints on A). The scala compiler is happy with the above signature, but I could not yet figure out how the implementation would look like - if it is possible at all.
Some options that came to my mind:
Do you have any other ideas that might work? Which way would you recommend? What kind of "challenges" to expect?
Or should I forget it, because it is not possible with the current Scala constraints?
Intention behind my problem: Say I have a business workflow, but it's not too strict. Some steps have fixed order, but others do not, but at the end all of them has to be done (or some of them required for further processing).
A bit more concrete example: I have an A, I can add B and C to it. I don't care which is done first, but at the end I'll need an A with B with C.
Comment: I don't know too much about Groovy but SO popped up this question and I guess it's more or less the same as what I'd like, at least conceptional.
In scala, trait mixins means you can extend any number of traits with a class or abstract class. You can extend only traits or combination of traits and class or traits and abstract class. It is necessary to maintain order of mixins otherwise compiler throws an error.
Traits are compile-time external values (rather than code generated from an external source). The difference is subtle. Mixins add logic, Traits add data such as compile-time type information.
Mixins are a form of object composition, where component features get mixed into a composite object so that properties of each mixin become properties of the composite object. The term “mixins” in OOP comes from mixin ice cream shops.
I believe this is impossible to do strictly at runtime, because traits are mixed in at compile-time into new Java classes. If you mix a trait with an existing class anonymously you can see, looking at the classfiles and using javap, that an anonymous, name-mangled class is created by 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
returns
Foo.class Main$.class Spam$class.class Main$$anon$1.class Main.class Spam.class
While javap Main\$\$anon\$1
returns
Compiled from "mixin.scala" public final class Main$$anon$1 extends Foo implements Spam{ public int eggs(); public Main$$anon$1(); }
As you can see, scalac creates a new anonymous class that is loaded at runtime; presumably the method eggs
in this anonymous class creates an instance of Spam$class
and calls eggs
on it, but I'm not completely sure.
However, we can do a pretty hacky trick here:
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
Since you need to use the Scala compiler, AFAIK, this is probably close to the cleanest solution you could do to get this. It's quite slow, but memoization would probably help greatly.
This approach is pretty ridiculous, hacky, and goes against the grain of the language. I imagine all sorts of weirdo bugs could creep in; people who have used Java longer than me warn of the insanity that comes with messing around with classloaders.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With