[groovy-user] metaclass and curry() gives MissingMethodException

16 views
Skip to first unread message

Bob Brown

unread,
Dec 29, 2008, 2:05:27 AM12/29/08
to us...@groovy.codehaus.org
To me, the following is unexpected (this is probably my inexperience shining
out, so please be gentle!). I couldn't find anything pointing me in the
right direction on the list archives.

I have the following test:

===
package au.com.transentia.utils.test

public class FastSplitTest extends GroovyTestCase {

void setUp() {
String.metaClass.fastSplit = {delim, expected ->
def buffer = []
try {
StringTokenizer st = new StringTokenizer(delegate, delim);
for (int i = 0; i < expected; i++)
buffer[i] = st.nextToken();
return buffer.toArray();
}
catch (NoSuchElementException e) {
throw new NoSuchElementException("Could not successfully split line:
'" + line + "' into " + expected + " items delimited by '" + delim + "'");
}
}

String.metaClass.fastSplitComma4 = String.metaClass.fastSplit.curry(",",
4)
}

void testSplit() {
assertArrayEquals(['this', 'is', 'a', 'test'].toArray(),
"this,is,a,test".fastSplit(",", 4))
}

void testCurried() {
assertArrayEquals(['this', 'is', 'a', 'test'].toArray(),
"this,is,a,test".fastSplitComma4())
}
}
===

This gives:
===
"C:\Program Files\Java\jdk1.6.0_11\bin\java" -Didea.launcher.port=7551
"-Didea.launcher.bin.path=C:\Program Files\JetBrains\IntelliJ IDEA
8.0.1\bin" -Dfile.encoding=windows-1252 -classpath
"C:\DEVTOOLS\groovy-1.6-RC-1\embeddable\groovy-all-1.6-RC-1.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\charsets.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\deploy.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\javaws.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\jce.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\jsse.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\management-agent.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\plugin.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\resources.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\rt.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\ext\dnsns.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\ext\localedata.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\ext\sunjce_provider.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\ext\sunmscapi.jar;C:\Program
Files\Java\jdk1.6.0_11\jre\lib\ext\sunpkcs11.jar;C:\DEVELOPMENT\IntelliJ\FB\
out\production\FB;C:\DEVTOOLS\commons-lang-2.4\commons-lang-2.4.jar;C:\DEVTO
OLS\groovy-1.6-RC-1\lib\junit-3.8.2.jar;C:\Program Files\JetBrains\IntelliJ
IDEA 8.0.1\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain
com.intellij.rt.execution.junit.JUnitStarter -ideVersion5
au.com.transentia.utils.test.FastSplitTest

groovy.lang.MissingMethodException: No signature of method:
groovy.lang.ExpandoMetaClass$ExpandoMetaProperty.curry() is applicable for
argument types: (java.lang.String, java.lang.Integer) values: {,, 4}
at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapt
er.java:54)
at
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSit
e.java:51)
at
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray
.java:43)
at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.
java:116)
at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.
java:128)
at
au.com.transentia.utils.test.FastSplitTest.setUp(FastSplitTest.groovy:21)
at
com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:25)
at
com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)


groovy.lang.MissingMethodException: No signature of method:
groovy.lang.ExpandoMetaClass$ExpandoMetaProperty.curry() is applicable for
argument types: (java.lang.String, java.lang.Integer) values: {,, 4}
at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapt
er.java:54)
at
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSit
e.java:51)
at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.
java:128)
at
au.com.transentia.utils.test.FastSplitTest.setUp(FastSplitTest.groovy:21)
at
com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:25)
at
com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)


Process finished with exit code -1
===

I also tried:

===
void setUp() {

def fastSplit = {delim, expected ->
def buffer = []
try {
StringTokenizer st = new StringTokenizer(delegate, delim);
for (int i = 0; i < expected; i++)
buffer[i] = st.nextToken();
return buffer.toArray();
}
catch (NoSuchElementException e) {
throw new NoSuchElementException("Could not successfully split line:
'" + line + "' into " + expected + " items delimited by '" + delim + "'");
}
}

String.metaClass.fastSplit = fastSplit
String.metaClass.fastSplitComma4 = fastSplit.curry(",", 4)
}
===

But...as expected...delegate gets set to FastSplitTest class and so once
gets:

===
groovy.lang.GroovyRuntimeException: Could not find matching constructor for:
java.util.StringTokenizer(au.com.transentia.utils.test.FastSplitTest,
java.lang.String)
at
groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1465)
at
groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1381)
at
org.codehaus.groovy.runtime.callsite.MetaClassConstructorSite.callConstructo
r(MetaClassConstructorSite.java:38)
at
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(Ca
llSiteArray.java:55)
at
org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrap
NoCoerce.callConstructor(ConstructorSite.java:108)
at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(Abstra
ctCallSite.java:203)
at
au.com.transentia.utils.test.FastSplitTest$_setUp_closure1.doCall(FastSplitT
est.groovy:26)
[...more elided...]
===

Is there a way of doing what I need here?

Comments/suggestions gratefully received.

Cheers,

BOB

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

http://xircles.codehaus.org/manage_email


Roshan Dawrani

unread,
Dec 29, 2008, 2:16:04 AM12/29/08
to us...@groovy.codehaus.org
Hi,

You can do it as below:

============================
def nonCurryClosure = {delim, expected ->

 def buffer = []
 try {
   StringTokenizer st = new StringTokenizer(delegate, delim);
   for (int i = 0; i < expected; i++)
     buffer[i] = st.nextToken();
   return buffer.toArray();
 }
 catch (NoSuchElementException e) {
   throw new NoSuchElementException("Could not successfully split line:'" + line + "' into " + expected + " items delimited by '" + delim + "'");
 }
}

String.metaClass.fastSplit = nonCurryClosure

println "this,is,a,test".fastSplit(",", 4)

String.metaClass.fastSplitComma4 = nonCurryClosure.curry(",", 4)

println "this,is,a,test".fastSplitComma4()
===============================

Hope it helps.
Roshan

Roshan Dawrani

unread,
Dec 29, 2008, 2:44:19 AM12/29/08
to us...@groovy.codehaus.org
Oh, oh..I missed the 2nd thing that you tried in the exception details.

I tried your first piece of code on 1.5.6 and mailed back. So it may be 1.6-RC-1 specific. I will try it on that version and get back if I find something.

Sorry.

Paul King

unread,
Dec 29, 2008, 4:29:43 AM12/29/08
to us...@groovy.codehaus.org

Hi Bob,

I haven't worked out a short way yet but this works:

---------------


String.metaClass.fastSplit = {delim, expected ->
def buffer = []
try {

def tokens = new StringTokenizer(delegate, delim)
for (int i = 0; i < expected; i++) buffer[i] = tokens.nextToken()
buffer.toArray()
}
catch (NoSuchElementException e) {
throw new NoSuchElementException("Could not successfully split line: '$line' into $expected items delimited by '$delim'")
}
}

def c = String.metaClass.methods.find{it.name=='fastSplit'}.closure.curry(",", 4)
String.metaClass.fastSplitComma4 << { c.delegate = delegate; c() }

println "this,is,a,test".fastSplit(",", 4)

println "this,is,a,test".fastSplitComma4()
---------------

We obviously need some clear short way to achieve the equivalent.


Paul.

Paul King

unread,
Dec 29, 2008, 4:38:29 AM12/29/08
to us...@groovy.codehaus.org

Or slightly tweaked but still long version:

===============


String.metaClass.fastSplit = {delim, expected ->
def buffer = []
try {
def tokens = new StringTokenizer(delegate, delim)

expected.times{ buffer[it] = tokens.nextToken() }
buffer.toArray()
}
catch (NoSuchElementException) {
def msg = "Could not successfully split line: '$delegate' into $expected items delimited by '$delim'"
throw new NoSuchElementException(msg)
}
}

def c = String.metaClass.methods.find{ it.name=='fastSplit' }.closure.curry(",", 4)
String.metaClass.fastSplitComma4 << { c.delegate = delegate; c() }

println "this,is,a,test".fastSplit(",", 4)
println "this,is,a,test".fastSplitComma4()

================

Paul.

Paul King

unread,
Dec 29, 2008, 4:50:30 AM12/29/08
to us...@groovy.codehaus.org

Yet another long version:

:::::::::::::::


String.metaClass.fastSplit = {delim, expected ->
def buffer = []
try {
def tokens = new StringTokenizer(delegate, delim)

expected.times{ buffer[it] = tokens.nextToken() }
buffer.toArray()
}
catch (NoSuchElementException) {

def msg = "Could not split '$delegate' into $expected items delimited by '$delim'"
throw new NoSuchElementException(msg)
}
}

def c = String.metaClass.pickMethod('fastSplit', null).closure.curry(",", 4)
String.metaClass.fastSplitComma4 = { c.delegate = delegate; c() }

println "this,is,a,test".fastSplit(",", 4)
println "this,is,a,test".fastSplitComma4()

:::::::::::::::


Paul.

Roshan Dawrani

unread,
Dec 29, 2008, 5:15:19 AM12/29/08
to us...@groovy.codehaus.org
Hi Paul,

So, what really is the reason for doing it like


String.metaClass.fastSplitComma4 = { c.delegate = delegate; c() }

I tried but am not able to understand on my own why is it needed to explicitly override the delegate of the curried closure?

Just a minor suggestion - If you want to avoid calls to pickMethod / find "fastSplit" from meta-class methods, etc and want to do it a slightly standard looking way, you can do:


def nonCurryClosure = {delim, expected ->
    ....
   }
   String.metaClass.fastSplit = nonCurryClosure
   def c = nonCurryClosure.curry(",", 4)

   String.metaClass.fastSplitComma4 = { c.delegate = delegate; c() }

rgds,
Roshan

Bob Brown

unread,
Dec 29, 2008, 6:29:51 AM12/29/08
to us...@groovy.codehaus.org
> Hi Paul,

Oh you show-off (and I mean that in the nicest way :-))! Multiple solutions,
no less...

>
> So, what really is the reason for doing it like
>
> String.metaClass.fastSplitComma4 = { c.delegate = delegate; c() }

I'm with Roshan here: why?

Interestingly (for me), this works without messing with delegates, so the
metaClass stuff is modifying life in some interesting way?:

===
public class Curries {

def spicy = { meat, sauce ->
println "${meat} with ${sauce}"
}

def chickenCurry = spicy.curry('chicken')
def chickenMadras = chickenCurry.curry('madras')

static void main(args) {
new Curries().chickenMadras()
// also OK:
// new Curries().spicy('beef', 'portugese sauce')
}
}
===

I'm also with you:

> We obviously need some clear short way to achieve the
> equivalent.

Cheers,

BOB

> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(Abstra
> ctCallSite.
> java:128)
> at
>
> au.com.transentia.utils.test.FastSplitTest.setUp(FastSplitTest.gr
> oovy:21)
> at
>
> com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.ja


> va:40)
> at
> sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> at
>
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorI
> mpl.java:39
> )
> at
>
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodA
> ccessorImpl
> .java:25)
> at
>
> com.intellij.rt.execution.application.AppMain.main(AppMain.java:9
> 0)
>
>

Jochen Theodorou

unread,
Dec 29, 2008, 9:39:01 AM12/29/08
to us...@groovy.codehaus.org
Roshan Dawrani schrieb:

> Hi Paul,
>
> So, what really is the reason for doing it like
>
> String.metaClass.fastSplitComma4 = { c.delegate = delegate; c() }

frankly... this looks strange... If all goes right, then c.delegate
should call CurriedClosure.setDelegate which does simply call
setDelegate on the uncurried closure. But the curried closure will
respond to getDelegate with its own delegate, rather then with
owner.delegate.. this sounds like a bug to me

bye blackdrag

--
Jochen "blackdrag" Theodorou
The Groovy Project Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/

Paul King

unread,
Dec 29, 2008, 5:02:44 PM12/29/08
to us...@groovy.codehaus.org

I suspect a bug too...

Paul.

--
View this message in context: http://www.nabble.com/metaclass-and-curry%28%29-gives-MissingMethodException-tp21199683p21209797.html
Sent from the groovy - user mailing list archive at Nabble.com.

Bob Brown

unread,
Dec 29, 2008, 5:19:43 PM12/29/08
to us...@groovy.codehaus.org
I have created http://jira.codehaus.org/browse/GROOVY-324, giving background
and referencing this thread.

BOB

Paul King

unread,
Jan 1, 2009, 1:13:08 AM1/1/09
to us...@groovy.codehaus.org

Hi Bob,

Thanks for finding/submitting the issue. Should be fixed in svn.
If you get a chance to double check with your examples that would
be great.

Paul.

Bob Brown

unread,
Jan 1, 2009, 1:20:50 AM1/1/09
to us...@groovy.codehaus.org
I saw you were beavering away...

I was most impressed ;-)

I will give it a try and see what I can see...

Happy new year to you and your family.

Hope the coming year isn't going to be all doom and gloom.

Cheers,

Bob Brown

unread,
Jan 6, 2009, 8:00:48 PM1/6/09
to us...@groovy.codehaus.org
http://jira.codehaus.org/browse/GROOVY-3242

I just tested against
http://bamboo.ci.codehaus.org/download/GROOVY-DEF/artifacts/build-175/Jars/g
roovy-all-1.6-RC-2-SNAPSHOT.jar

All is now OK.

Great work, thank you.

BOB

Reply all
Reply to author
Forward
0 new messages