[groovy-user] Some @Mixin woes

41 views
Skip to first unread message

OC

unread,
Feb 3, 2013, 11:41:03 AM2/3/13
to user@groovy.codehaus.org User
Hello there,

I've started to use @Mixin a bit more extensively, and I've bumped into three problems. Can you Groovy Gurus please check whether this is the intended behaviour, or whether I should raise jiras for them? Looks like (i) might be a bug, (ii) and (iii) feature requests; but as always, I might be overlooking something of importance...

(i) in mixed-in methods, if 'this' is used to call method, it's right (I've used explicit "this.", works same way with implicit of course); if its used in other way, it's wrong though:

112 /tmp> <t1.groovy
class Mix {
def foo() { println "Mixed-in method thinks we are in $this" ; this.bar() }
}
class Root {
def bar() { println "Though the 'right' this is, through inheritance, self-evidently known to be $this" }
}
@Mixin(Mix) class Foo extends Root {}

new Foo().foo()
113 /tmp> groovy t1
Mixed-in method thinks we are in Mix@76027982
Though the 'right' this is, through inheritance, self-evidently known to be Foo@1f57ea4a

(ii) static methods do not mix at all:

114 /tmp> <t2.groovy
class Mix {
static def foo() { println "static not mixed in at all" }
}
@Mixin(Mix) class Foo {}

Foo.foo()
115 /tmp> groovy t2
Caught: groovy.lang.MissingMethodException: No signature of method: static Foo.foo() is applicable for argument types: () values: []

(iii) static typechecker seems to ignore @Mixin completely:

116 /tmp> <t3.groovy
class Mix {
def foo() { println "Mixed-in method ignored by static checker" }
}
@groovy.transform.TypeChecked @Mixin(Mix) class Foo {
def bar() { foo() }
}

new Foo().bar()
117 /tmp> groovy t3
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/private/tmp/t3.groovy: 5: [Static type checking] - Cannot find matching method Foo#foo(). Please check if the declared type is right and if the method exists.
@ line 5, column 15.
def bar() { foo() }
^
1 error

Thanks and all the best,
OC


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email


Cédric Champeau

unread,
Feb 4, 2013, 4:18:54 AM2/4/13
to us...@groovy.codehaus.org

Le 03/02/2013 17:41, OC a �crit :
> Hello there,
hi !
> I've started to use @Mixin a bit more extensively, and I've bumped into three problems. Can you Groovy Gurus please check whether this is the intended behaviour, or whether I should raise jiras for them? Looks like (i) might be a bug, (ii) and (iii) feature requests; but as always, I might be overlooking something of importance...
>
> (i) in mixed-in methods, if 'this' is used to call method, it's right (I've used explicit "this.", works same way with implicit of course); if its used in other way, it's wrong though:
>
> 112 /tmp> <t1.groovy
> class Mix {
> def foo() { println "Mixed-in method thinks we are in $this" ; this.bar() }
> }
> class Root {
> def bar() { println "Though the 'right' this is, through inheritance, self-evidently known to be $this" }
> }
> @Mixin(Mix) class Foo extends Root {}
>
> new Foo().foo()
> 113 /tmp> groovy t1
> Mixed-in method thinks we are in Mix@76027982
> Though the 'right' this is, through inheritance, self-evidently known to be Foo@1f57ea4a
you need to access the mixed in instance instead of "this". "this"
references the mixin class, not the mixed in one. From the mixin class,
you can get the mixed in one by calling metaClass.owner
> (ii) static methods do not mix at all:
>
> 114 /tmp> <t2.groovy
> class Mix {
> static def foo() { println "static not mixed in at all" }
> }
> @Mixin(Mix) class Foo {}
>
> Foo.foo()
> 115 /tmp> groovy t2
> Caught: groovy.lang.MissingMethodException: No signature of method: static Foo.foo() is applicable for argument types: () values: []
Yes, it's unsupported.
> (iii) static typechecker seems to ignore @Mixin completely:
>
> 116 /tmp> <t3.groovy
> class Mix {
> def foo() { println "Mixed-in method ignored by static checker" }
> }
> @groovy.transform.TypeChecked @Mixin(Mix) class Foo {
> def bar() { foo() }
> }
>
> new Foo().bar()
> 117 /tmp> groovy t3
> org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
> /private/tmp/t3.groovy: 5: [Static type checking] - Cannot find matching method Foo#foo(). Please check if the declared type is right and if the method exists.
> @ line 5, column 15.
> def bar() { foo() }
> ^
> 1 error
Ok, one thing you have to understand, which explains a lot of things, is
that @Mixin is a *runtime mixin*, not a trait or compile-time mixin. It
comes with a lot of drawbacks, including, of course, the fact that it's
not compatible with type checking...
> Thanks and all the best,
> OC
>
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
> http://xircles.codehaus.org/manage_email
>
>
>


--
C�dric Champeau
SpringSource - A Division Of VMware
http://www.springsource.com/
http://twitter.com/CedricChampeau

Ondřej Čada

unread,
Feb 4, 2013, 6:01:47 AM2/4/13
to us...@groovy.codehaus.org
Cédric,

On Feb 4, 2013, at 10:18 AM, Cédric Champeau wrote:

> you need to access the mixed in instance instead of "this". "this" references the mixin class, not the mixed in one. From the mixin class, you can get the mixed in one by calling metaClass.owner
...
> Ok, one thing you have to understand, which explains a lot of things, is that @Mixin is a *runtime mixin*, not a trait or compile-time mixin. It comes with a lot of drawbacks, including, of course, the fact that it's not compatible with type checking...


I see. Actually I thought it's a compile-time thing (unlike the runtime mixin method). So much to learn :)

Just for the record, I've tentatively tried to write my own AST which does the compile-time mix (source for reference below -- if you or anyone see some blunder in there which might bit my behind in future, I'll be very glad if you could warn me), and TypeChecked seems to work all right. Groovy rocks indeed, just as you people who work on it!

Thanks a very big lot,
---
Ondra Čada
OCSoftware: o...@ocs.cz http://www.ocs.cz
private on...@ocs.cz http://www.ocs.cz/oc

=== AST source sans the self-evident import stuff
package cz.ocs

@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.TYPE])
@GroovyASTTransformationClass (["cz.ocs.StaticMixinASTTransformation"])
public @interface StaticMixin {
Class[] value()
}

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class StaticMixinASTTransformation implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
nodes?.each { if (it instanceof ClassNode) visitClass(it) }
}
private void mixinClassToClass(categoryNode,classNode) {
categoryNode.methods?.each {
classNode.addMethod(it)
}
categoryNode.properties?.each {
classNode.addProperty(it)
}
categoryNode.fields?.each {
classNode.addField(it)
}
}
private void visitClass(classNode) {
def category=classNode.annotations?.find { it.classNode.name=="cz.ocs.StaticMixin" }
def expr=category?.getMember('value')
if (expr instanceof ClassExpression) mixinClassToClass(expr.type,classNode)
else if (expr instanceof ListExpression) expr?.expressions?.each { mixinClassToClass(it.type,classNode) }
else println "Unknown annotation value: ${expr.getClass()} -> ${expr}"

Ondřej Čada

unread,
Feb 4, 2013, 6:40:55 AM2/4/13
to Groovy_Users
Hello there,

On Feb 4, 2013, at 12:01 PM, Ondřej Čada wrote:

> Just for the record, I've tentatively tried to write my own AST which does the compile-time mix (source for reference below -- if you or anyone see some blunder in there which might bit my behind in future, I'll be very glad if you could warn me), and TypeChecked seems to work all right. Groovy rocks indeed, just as you people who work on it!

Hm, I've bumped into a problem quite soon :(

So as the static type checking works both in the class into which the methods are injected and also in the "donor" classes (which of course can call methods from the main class), I am mixing all the methods back to the donors.

And it does not work in a weird way: compiles all right, the static type checker checks all OK, but at runtime, it fails:

=== the AST (sans boilerplate)
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class StaticMixinASTTransformation implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
nodes?.each { if (it instanceof ClassNode) visitClass(it) }
// has to add back all methods to all categories for static type checker
catsByClass.each { classNode,categories ->
categories.each { mixinClassToClass(classNode,it,false) }
}
}
private def catsByClass=[:].withDefault {[]}
private void mixinClassToClass(categoryNode,classNode,record=true) {
println "from $categoryNode.name to $classNode.name (record $record)"
if (record) catsByClass[classNode]<<categoryNode
categoryNode.methods?.each {
println " adding $it.name (${classNode.getMethod(it.name,it.parameters)?'exists':'new'})"
if (!classNode.getMethod(it.name,it.parameters))
classNode.addMethod(it) //*
}
categoryNode.properties?.each {
classNode.addProperty(it)
}
categoryNode.fields?.each {
classNode.addField(it)
}
}
private void visitClass(classNode) {
def category=classNode.annotations?.find { it.classNode.name=="cz.ocs.StaticMixin" }
def expr=category?.getMember('value')
if (expr instanceof ClassExpression) mixinClassToClass(expr.type,classNode)
else if (expr instanceof ListExpression) expr?.expressions?.each { mixinClassToClass(it.type,classNode) }
else println "Unknown annotation value: ${expr.getClass()} -> ${expr}"
}
}
=== the test source
@groovy.transform.TypeChecked class E {
def foo() { println "E's foo $this";bax() }
static def bar() { println "bar $this";baz() }
}
@groovy.transform.TypeChecked @StaticMixin([E]) class C {
def bax() { println "bax $this" }
static def baz() { println "baz $this" }
}
@groovy.transform.TypeChecked class Test {
static void main(av) {
new C().foo()
C.bar()
}
}
=== the log
============= compiling Test ===
from E to C (record true)
adding foo (new)
adding bar (new)
from C to E (record false)
adding bax (new)
adding baz (new)
adding foo (exists)
adding bar (exists)
============= running ../Test.groovy ===
Exception in thread "main" java.lang.VerifyError: (class: C, method: foo signature: ()Ljava/lang/Object;) Incompatible object argument for function call
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
at java.lang.Class.getDeclaredConstructors(Class.java:1836)
at org.codehaus.groovy.reflection.CachedClass$2$1.run(CachedClass.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:66)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:64)
at org.codehaus.groovy.util.LazyReference.getLocked(LazyReference.java:46)
at org.codehaus.groovy.util.LazyReference.get(LazyReference.java:33)
at org.codehaus.groovy.reflection.CachedClass.getConstructors(CachedClass.java:258)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:189)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:193)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createNormalMetaClass(MetaClassRegistry.java:157)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:147)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:130)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:175)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:192)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:309)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:806)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:84)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186)
at Test.main(Test.groovy:21)
===

Can you perhaps see what's wrong and advice how to fix it? Suspecting the cross-adding methods would be the culprit, I've tried also

classNode.addMethod(record?it:new MethodNode(it.name,it.modifiers,it.returnType, it.parameters,it.exceptions,EmptyStatement.INSTANCE))

at the //* line, but it did not help either :(

Thanks a big lot,
---
Ondra Čada
OCSoftware: o...@ocs.cz http://www.ocs.cz
private on...@ocs.cz http://www.ocs.cz/oc




Ondřej Čada

unread,
Feb 4, 2013, 7:15:45 AM2/4/13
to Groovy_Users
Hello there again,

On Feb 4, 2013, at 12:40 PM, Ondřej Čada wrote:

> And it does not work in a weird way: compiles all right, the static type checker checks all OK, but at runtime, it fails:...

Found the culprit -- in fact, should have seen it sooner: when main class methods get injected to the donor, they also get called from the donor instead of the main class' ones. Well, looks like today's my dumb day. As usual :)

Perhaps I'm completely on a wrong track. How do you people in Groovy split big class into a number of separate sources, each of which contains some properties and some methods, grouped by functionality -- the task for which Objective C uses its categories (quite different from Groovy ones)?

Thanks and all the best,

OC

unread,
Feb 4, 2013, 10:59:24 AM2/4/13
to Groovy_Users
Erm, back to ...

On Feb 4, 2013, at 1:15 PM, Ondřej Čada <o...@ocs.cz> wrote:

> Found the culprit -- in fact, should have seen it sooner: when main class methods get injected to the donor, they also get called from the donor instead of the main class' ones. Well, looks like today's my dumb day. As usual :)

... looks like my dumb day indeed and it's far from self-evident. Quite the contrary, it stinks -- either I'm making some big mistake, or there's a problem somewhere at the metaclass level. If Cédric (or anybody else who understands metaclass and ASTs) happens to have a moment, can you perhaps please check this very trivial script:

=== Test.groovy ===
import cz.ocs.*
class Donor {
def foo() { bar() }
def bar() {}
}
@StaticMixin(Donor)
class Recipient {
def bar() {}
}
new Recipient()
=== End ===

When an AST which injects the method foo from donor to recipient is used, the script crashes. The exception is caused just by instantiaton and is somewhere in the metaclass creation code.

Here is the full AST source (moved up to CompilePhase.CONVERSION so as I don't bump into scope problems; correct me please if I am wrong, but I believe the scopes will be created later on?), this time including boilerplate so that you can easily check yourself by plain copy/paste:

=== AST.groovy ===
package cz.ocs
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.transform.*
import org.codehaus.groovy.control.*

public @interface StaticMixin { Class[] value() }

@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
public class StaticMixinAST implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
source?.ast?.classes?.each { if (it instanceof ClassNode) visitClass(it,source?.ast?.classes) }
}
private void visitClass(classNode,allClasses) {
def category=classNode.annotations?.find { it.classNode.unresolvedName=="StaticMixin" }
if (!category) return
def expr=category?.getMember('value')
def clazz=allClasses.find { expr.name==it.name }
if (clazz) mixinClassToClass(clazz,classNode)
}
private void mixinClassToClass(categoryNode,classNode) {
println "from $categoryNode.name to $classNode.name"
categoryNode.methods?.each {
if (classNode.getMethod(it.name,it.parameters)) println " not adding existing $it.text"
else {
println " adding $it.text"
classNode.addMethod(it)
}
}
}
}
=== End ===

You would know how to create and run the test better than me :), but just for reference, here's the exact way I've used -- and the result I am getting with Groovy 2.1 and Mac OS X 10.8:

167 /tmp> groovyc AST.groovy -d jardir
168 /tmp> mkdir -p jardir/META-INF/services
169 /tmp> echo cz.ocs.StaticMixinAST > jardir/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
170 /tmp> jar -cf ast.jar -C jardir .
171 /tmp> groovy -cp ast.jar -d Test
from Donor to Recipient
adding public java.lang.Object foo() { ... }
not adding existing public java.lang.Object bar() { ... }
Caught: java.lang.VerifyError: (class: Recipient, method: foo signature: ()Ljava/lang/Object;) Incompatible object argument for function call
java.lang.VerifyError: (class: Recipient, method: foo signature: ()Ljava/lang/Object;) Incompatible object argument for function call
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
at java.lang.Class.getDeclaredConstructors(Class.java:1836)
at org.codehaus.groovy.reflection.CachedClass$2$1.run(CachedClass.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:66)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:64)
at org.codehaus.groovy.util.LazyReference.getLocked(LazyReference.java:46)
at org.codehaus.groovy.util.LazyReference.get(LazyReference.java:33)
at org.codehaus.groovy.reflection.CachedClass.getConstructors(CachedClass.java:258)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:189)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:193)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createNormalMetaClass(MetaClassRegistry.java:157)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:147)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:130)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:175)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:192)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:309)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:806)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:84)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186)
at Test.run(Test.groovy:10)
at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:257)
at groovy.lang.GroovyShell.run(GroovyShell.java:220)
at groovy.lang.GroovyShell.run(GroovyShell.java:150)
at groovy.ui.GroovyMain.processOnce(GroovyMain.java:588)
at groovy.ui.GroovyMain.run(GroovyMain.java:375)
at groovy.ui.GroovyMain.process(GroovyMain.java:361)
at groovy.ui.GroovyMain.processArgs(GroovyMain.java:120)
at groovy.ui.GroovyMain.main(GroovyMain.java:100)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:106)
at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:128)
172 /tmp>

I'd be pretty grateful for any insight.

Thanks a big lot,
OC

Cédric Champeau

unread,
Feb 5, 2013, 3:24:51 AM2/5/13
to us...@groovy.codehaus.org
Hi!

The problem with your AST transform is that you cannot just take a
method from one AST node to another. There are multiple issues with
this: first, if both of the classes (the mixed in and the mixin class)
are compiled at the same time, there are chances that one method node
may not have a method body when the AST transform is run. Another is
that in general, with precompiled classes (think of mixing a precompiled
class), the method nodes that represent the methods of the mixed in
class are just *stubs* for the real methods, without an AST (because we
don't construct an AST from bytecode, that would be horribly complex and
slow).

Therefore, I suggest you take another strategy, like @Delegate at the
class level with multiple @Delegates. At least, that's the easiest way I
can think of for trait-like feature in Groovy. The idea is to generalize
the @Delegate AST transformation so that it works not only for fields,
but also at the class level. Actually, there have been several
discussions in the past few weeks about it and it looks like porting the
Grails org.codehaus.groovy.grails.compiler.injection.MixinTransformation
into groovy-core is a good idea because it does that (porting with
another name, probably @Trait).
--
Cédric Champeau

Guillaume Laforge

unread,
Feb 5, 2013, 3:36:35 AM2/5/13
to Groovy User
+1 on the Grails @Mixin to become a Groovy @Trait.
--
Guillaume Laforge
Groovy Project Manager
Head of Groovy Development at SpringSource
http://www.springsource.com/g2one

Jochen Theodorou

unread,
Feb 5, 2013, 3:44:05 AM2/5/13
to us...@groovy.codehaus.org
Am 05.02.2013 09:36, schrieb Guillaume Laforge:
> +1 on the Grails @Mixin to become a Groovy @Trait.

Groovy++ had a trait annotation as well... I wonder how they compare.

bye blackdrag

--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org

Guillaume Laforge

unread,
Feb 5, 2013, 3:55:24 AM2/5/13
to Groovy User

Jochen Theodorou

unread,
Feb 5, 2013, 4:31:14 AM2/5/13
to us...@groovy.codehaus.org
Am 05.02.2013 09:55, schrieb Guillaume Laforge:
> Here it is, for reference:
> https://github.com/groovypp/groovypp/blob/master/Compiler/src/org/mbte/groovypp/compiler/TraitASTTransform.groovy

I think http://code.google.com/p/groovypptest/wiki/Traits is better as
explanation, but the principle is different imho. To me it looks a lot
like based on categories. Basing on @Delegate is a very different thing.
I don't know about the grails implementation, but we could depend on an
instance. In Groovypp you mark a class to become a trait, basing on
@Delegate means any class can become a trait. And the mixin
transformation is very much like that imho.

So what is the difference between mixin and trait? In the end maybe only
that you declare a trait, but only use a mixin. If I am right with that,
then we should not call it traits

Maarten Boekhold

unread,
Feb 5, 2013, 6:19:52 AM2/5/13
to us...@groovy.codehaus.org
On 02/05/2013 01:31 PM, Jochen Theodorou wrote:=
> So what is the difference between mixin and trait? In the end maybe
> only that you declare a trait, but only use a mixin. If I am right
> with that, then we should not call it traits
>

I'll probably make a fool of myself here now, but I've read a definition
somewhere that a Mixin only adds behavior, while a Trait can add
state... I think that's how it works in Scala at least. Don't have any
references for that at hand though at the moment.

Maarten

Jochen Theodorou

unread,
Feb 5, 2013, 6:56:31 AM2/5/13
to us...@groovy.codehaus.org
Am 05.02.2013 12:19, schrieb Maarten Boekhold:
> On 02/05/2013 01:31 PM, Jochen Theodorou wrote:=
>> So what is the difference between mixin and trait? In the end maybe
>> only that you declare a trait, but only use a mixin. If I am right
>> with that, then we should not call it traits
>
> I'll probably make a fool of myself here now, but I've read a definition
> somewhere that a Mixin only adds behavior, while a Trait can add
> state... I think that's how it works in Scala at least. Don't have any
> references for that at hand though at the moment.

I think a Mixin can have a state too. I think more that traits are based
on interfaces, while Mixin can, but don't have to. Also I think what we
call mixin in Groovy is probably not 100% a mixin since a mixin is not
supposed to be a standalone class.

bye blackdrag

--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org


Ondřej Čada

unread,
Feb 5, 2013, 9:30:29 AM2/5/13
to us...@groovy.codehaus.org
Cédric,

On Feb 5, 2013, at 9:24 AM, Cédric Champeau wrote:

> The problem with your AST transform is that you cannot just take a method from one AST node to another.

Well, let's for the moment forget my concrete implementation, which was faulty, and let's focus on the goal. The goal is to split implementation of a big and complex class into more source files -- somewhat like Objective-C categories (different from Groovy categories!) do.

Imagine you

(i) wrote a Donor class, but not add it to project -- keep it separately
(ii) copy/pasted its complete contents to the Receiver class source
(iii) removed from the pasted code methods and properties which would collide with existing ones.

No more, no less.

How to automate/ASTate this thing in Groovy? To support, among others, static type checks, and of course, that the code is actually written only once, and if I change Donor and re-build, the change gets applied to all Receivers?

> Therefore, I suggest you take another strategy, like @Delegate at the class level with multiple @Delegates.

@Delegate does conceptually different thing: it adds a Donor's instance into Receiver, and redirects Receiver's methods to that instance.

That's something completely different.

For one, it does not allow Donor's methods to call methods of Receiver (and of other Donors).

Also, it created bogus instances which mess things up and might cause hard-to-find errors e.g., if metaclass dynamism comes into that.

Ideally there should be just one class, Receiver, whose instances(*) simply contain all the methods defined in it and it all its Donors. Ideally, Donor classes should not exist in the jar. If they do, they would be ignored and never ever instantiated.

(*) The class itself too, of course -- static methods and properties of Donor would go to Receiver the very same way the instance ones.

> At least, that's the easiest way I can think of for trait-like feature in Groovy. The idea is to generalize the @Delegate AST transformation so that it works not only for fields, but also at the class level.

I've considered this approach and I fear it is not valid, for the reasons outlined above.

> Actually, there have been several discussions in the past few weeks about it and it looks like porting the Grails org.codehaus.groovy.grails.compiler.injection.MixinTransformation into groovy-core is a good idea because it does that (porting with another name, probably @Trait).

Thanks pointing it out, I'll check how the transformation works.

Thanks a very big lot for all the information, insight and help,

Ondřej Čada

unread,
Feb 5, 2013, 9:40:49 AM2/5/13
to us...@groovy.codehaus.org
Jochen,

On Feb 5, 2013, at 10:31 AM, Jochen Theodorou wrote:

> ...I don't know about the grails implementation, but we could depend on an instance.

I am not entirely sure what this means, but I actually think we can't depend on any instance. The "donor" classes should add their complete behaviour to the "receiver" classes, even if there is no instance at all.

> In Groovypp you mark a class to become a trait, basing on @Delegate means any class can become a trait. And the mixin transformation is very much like that imho.

I haven't studied the linked resources yet, so I might be overlooking something of importance; but in general, it would be quite right and beneficial to mark the donors (instead of the receivers), to achieve the goal

On Feb 5, 2013, at 3:30 PM, Ondřej Čada wrote:

> Ideally there should be just one class, Receiver, whose instances(*) simply contain all the methods defined in it and it all its Donors. Ideally, Donor classes should not exist in the jar. If they do, they would be ignored and never ever instantiated.
>
> (*) The class itself too, of course -- static methods and properties of Donor would go to Receiver the very same way the instance ones.

It looks somewhat similar to the Groovy 2 extensions, but for that

(a) extensions, far as I understand, must come from an external JAR, for they are declared in its manifest, and they can't be part of the project itself
(b) they are implemented at runtime, not compile-time.

Or am I wrong about the extensions? I regret to say I haven't had yet time to grok their technical details fully -- so much to learn, and vita brevis...

Thanks and all the best
---
Ondra Čada
OCSoftware: o...@ocs.cz http://www.ocs.cz
private on...@ocs.cz http://www.ocs.cz/oc




Cédric Champeau

unread,
Feb 5, 2013, 9:42:19 AM2/5/13
to us...@groovy.codehaus.org

Imagine you

(i) wrote a Donor class, but not add it to project -- keep it separately
(ii) copy/pasted its complete contents to the Receiver class source
(iii) removed from the pasted code methods and properties which would collide with existing ones.

No more, no less.

How to automate/ASTate this thing in Groovy? To support, among others, static type checks, and of course, that the code is actually written only once, and if I change Donor and re-build, the change gets applied to all Receivers?
Well, that's what I explained, you cannot. As simple as that. There's no reason for a compiler to have access to source code of dependencies, that is, Donor.

.
Thanks pointing it out, I'll check how the transformation works.
Also look at the branch I just pushed here, which is an experiment for traits/mixins that was started by your thread :)

https://github.com/melix/groovy-core/commits/trait-experiment

Ondřej Čada

unread,
Feb 5, 2013, 9:59:27 AM2/5/13
to us...@groovy.codehaus.org
Cédric,

On Feb 5, 2013, at 3:42 PM, Cédric Champeau wrote:

>> Imagine you
>>
>> (i) wrote a Donor class, but not add it to project -- keep it separately
>> (ii) copy/pasted its complete contents to the Receiver class source
>> (iii) removed from the pasted code methods and properties which would collide with existing ones.
>>
>> No more, no less.
>>
>> How to automate/ASTate this thing in Groovy? To support, among others, static type checks, and of course, that the code is actually written only once, and if I change Donor and re-build, the change gets applied to all Receivers?
>>
> Well, that's what I explained, you cannot. As simple as that. There's no reason for a compiler to have access to source code of dependencies, that is, Donor.

OK, let me re-phrase: how to automate/ASTate this thing in Groovy, presumed all the receivers and donors are in the same project to be build at the same time to go to the same JAR?

(Might be just a clumsy way to say "in the same compilation unit", but I keep at the safe side in case I am missing something important of compilation units :))

> Also look at the branch I just pushed here, which is an experiment for traits/mixins that was started by your thread :)
>
> https://github.com/melix/groovy-core/commits/trait-experiment

Will do, thanks! Perhaps it's an answer to my questions, so I won't write more here until I check this and the other links :)

Thanks again a very big lot for all the help,
---
Ondra Čada
OCSoftware: o...@ocs.cz http://www.ocs.cz
private on...@ocs.cz http://www.ocs.cz/oc




Jochen Theodorou

unread,
Feb 5, 2013, 10:06:51 AM2/5/13
to us...@groovy.codehaus.org
Am 05.02.2013 15:30, schrieb Ondřej Čada:
[...]
> (i) wrote a Donor class, but not add it to project -- keep it
> separately (ii) copy/pasted its complete contents to the Receiver
> class source (iii) removed from the pasted code methods and
> properties which would collide with existing ones.

so you actually want a kind of template?

bye blackdrag
--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org


Ondřej Čada

unread,
Feb 5, 2013, 10:34:47 AM2/5/13
to us...@groovy.codehaus.org
Jochen,

On Feb 5, 2013, at 4:06 PM, Jochen Theodorou wrote:

> Am 05.02.2013 15:30, schrieb Ondřej Čada:
> [...]
>> (i) wrote a Donor class, but not add it to project -- keep it
>> separately (ii) copy/pasted its complete contents to the Receiver
>> class source (iii) removed from the pasted code methods and
>> properties which would collide with existing ones.
>
> so you actually want a kind of template?

Well depends on what you understand a "template" is, but my primary highest-level goal is to split implementation of a big and complex class into more source files -- somewhat like Objective-C categories (different from Groovy categories!) do.

In other words, instead of having in my project

// Foo.groovy, hundreds of source lines, ugly!
class Foo {
... methods (both static and instance) and properties related to X ...
... methods (both static and instance) and properties related to Y ...
... methods (both static and instance) and properties related to Z ...
...
}

I'd like to have in the project something like

// Foo_X.groovy, decent size
@ForClass(Foo) class Part_X {
... methods (both static and instance) and properties related to X ...
}
// Foo_Y.groovy, decent size
@ForClass(Foo) class Part_Y {
... methods (both static and instance) and properties related to Y ...
}
// Foo_Z.groovy, decent size
@ForClass(Foo) class Part_Z {
... methods (both static and instance) and properties related to Z ...
}
...

ideally -- as far as possible -- this should build essentially the same way (so far as other ASTs, static type-checking etc. works as well as with the monolitic Foo) and have essentially the same result as the monolitic Foo.groovy above would. Best theoretical outcome, no Part_X.class-es at all, only one big Foo.class with the same contents as it would have in the monolitic case. Of course, any practical solution with Part_X.classes, based preferrably on compile-time changes (so that type-checking etc can work), or at worst based on runtime trickery, still much better than nothing. In fact my StaticMixin does _SOMEWHAT_ works this way, but is unreliable and dangerous.

That this is not possible (at least, I don't know how to), is at the moment my biggest problem (well along with the bloody Java non-virtual static method inheritance possible work-arounds of which we debated some time ago; but that's quite another story).

A very nice added benefit (which Objective-C categories can't do, but I can achieve this in ObjC in slightly different way using something vaguely similar to Groovy methods in interfaces) would be to allow some of the parts being "injected to" different receivers, like

// Bar_X.groovy
@ForClasses([Foo,Bar,Bax]) class Shared_Part_X {
... methods (both static and instance) and properties which all the three classes have ...
}

This of course would be somewhat difficult to properly type-check (though possible, the typecheck would have to stick with the common subset of API of all the named classes).

Anyway, I've promised I'd keep out of this debate until I learn the things you and Cédric linked, so I'm going to do exactly that :)

Thanks again a _very big_ lot,
---
Ondra Čada
OCSoftware: o...@ocs.cz http://www.ocs.cz
private on...@ocs.cz http://www.ocs.cz/oc




Reply all
Reply to author
Forward
0 new messages