Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is Groovy doing here?

I was trying to debug some code that uses mixins and I was able to reduce my problem down to this example. I have a parent class that receives methods via a mixin and a child class that inherits from the parent. If I try to replace a method on an instance of the child class it works UNLESS the method that I'm replacing was called on an instance of the parent class before it is replaced. If it has been called then I can't replace it

So this code:

class M {
    protected foo() { println 'foo' }
}

@Mixin(M) class A {
 def bar() { foo() }
}

class B extends A {}

def b = new B()
def a = new A()
a.bar() //<-- comment out this line and see the difference
b.metaClass.foo = {println 'winning'}
b.bar()

Will yield:

foo

foo

But if you comment out line 13 (the one with the comment that says to comment it out) you'll get:

winning

Why does this happen? I expect there is some way this makes sense within the context of Groovy's metaclass model, but I don't get it.

This is Groovy 1.8.6

like image 884
mfollett Avatar asked Mar 01 '12 22:03

mfollett


1 Answers

The metaClass is looked upon on a method call and mixins have their own handler. Both are lazily loaded, and static, if you do not call a method the static lazy loading does not happen.
Mixins take precedence over metaClass overrides which is why it displays foo and not winning if you initialize A.
A meta is defined on the object it is applied to, in order for it to be resolved per class you need Object.class.metaClass (i.e here B.metaClass). Interestingly this yields :

groovy.lang.MissingMethodException: No signature of method: B.foo() is applicable for argument types: () values: []
Possible solutions: foo(), foo(java.lang.Object), bar(), any(), use([Ljava.lang.Object;), find(groovy.lang.Closure)

Defining foo on B resolve the error :

class B extends A {
    def foo() { println 'not winning' }
}

Your answer is that the Mixin affects the class metastore, and class methods take precedence over object metastore methods.

Proof :

@Mixin(M)
class B extends A {

}

a.bar() //<-- comment out this line and see the difference
B.metaClass.foo = {println 'class winning'}
b.metaClass.foo = {println 'object winning'}
b.bar()

Yields :

foo
class winning

A different approach

class M {
    protected foo() { println 'foo' }
}

@Mixin(M) class A {
 def bar() { foo() }
}

class B extends A {
    def bar() { foo() }
}

class C extends B {
    def foo() { println 'wat' }
}

@Mixin(M)
class D extends C { }

def b = new B()
def a = new A()
def c = new C()
def d = new D()


a.bar() //<-- comment out this line and see the difference
b.metaClass.foo = {println 'winning'}
b.bar()

c.metaClass.foo = {println 'losing'}
c.bar()

d.metaClass.foo = {println 'draw'}
d.bar()

Yields

foo
winning
wat
wat
like image 94
Gepsens Avatar answered Sep 28 '22 03:09

Gepsens