Skip to content

Instantly share code, notes, and snippets.

@purplefox
Created April 30, 2013 07:08
Show Gist options
  • Save purplefox/5487084 to your computer and use it in GitHub Desktop.
Save purplefox/5487084 to your computer and use it in GitHub Desktop.
Why can't I call this method which has default argument values like this? Am I misunderstanding how Groovy default values work?
void deployModule(String moduleName, Map<String, Object> config = [:], int instances = 1, Closure doneHandler = {}) {
}
deployModule("foo", {} )
@robfletcher
Copy link

Yes, unfortunately you must provide actual parameters for all formal params up to the last one you want to override the default for.

e.g.

deployModule "foo", [:]

would work but

deployModule "foo", 2

would not.

@robfletcher
Copy link

You can do pseudo named arguments by accepting a single map parameter but then of course you're responsible for any type validation.

@fix
Copy link

fix commented Apr 30, 2013

yes, something like

 deployModule(moduleName:'bla',instances:2)

It favors also best practice at naming parameters...

@purplefox
Copy link
Author

Hmm ok.

I would have expected Groovy to infer that I want to call the method with default values for config and instances since there is no ambiguity in this case.

Thanks for the info!

@purplefox
Copy link
Author

But there's another problem...

I accept the fact that default params don't work the way I like so add the following method so I can call it how I would like:

@purplefox
Copy link
Author

void deployModule(String moduleName, Closure doneHandler = {}) {
}

But then it barfson compile saying method with that signature is already defined. wtf?

@antoineroux
Copy link

I think this works only if doneHandler has no default arguments. If you don't do that, as you have three parameters with default arguments, I think Groovy tries to apply the closure two the first argument, which does not match.

It could probably try to guess what argument is expected. But then what "config" has type object? Should it take the argument instead of doneHandler? It probably be enhanced, but the edge cases become quickly difficult.

@antoineroux
Copy link

Too slow to answer :-D

@fix
Copy link

fix commented Apr 30, 2013

well, yes.

But what the point of defining another function with less parameters in signature, when there is already one that default the missing parameters ?

@antoineroux
Copy link

About your last comment: this is the JVM. It wants strongly typed methods. To handle that, Groovy generates several methods for deployModule that in fact accept 1, 2, 3 or 4 arguments (you can see that in the generated bytecode or in an IDE, for instance the Overview view in Eclipse). So it collides with the methods you define later.

I think you have two options: the named arguments style, which will avoid to create several methods, or defining by hand all the method calls you want to allow.

@antoineroux
Copy link

@fix: I think he wants to allow the caller to pick what arguments they want to specify and leave others to the default.

@fix
Copy link

fix commented Apr 30, 2013

see here http://groovyconsole.appspot.com/edit/955001

if you add default to callback, which is a better naming than doneHandler btw :), you have error compilation, but what the point?

@purplefox
Copy link
Author

This doesn't make a lot of sense to me. It seems there is no ambiguity here so Groovy should be able to call it.

Also, regarding generated methods, I guess Groovy generates the following:

void deployModule(String moduleName, Map<String, Object> config = [:], int instances = 1, Closure doneHandle)
void deployModule(String moduleName, Map<String, Object> config = [:], int instances = 1)
void deployModule(String moduleName, Map<String, Object> config = [:])
void deployModule(String moduleName)

None of the above should collide with:

void deployModule(String moduleName, Closure doneHandle)

So I don't see why I can't add the method.

It seems to me the only way I can fix this is to not use default arguments at all, and just overload the method multiple times like we do in Java.

But I thought the whole point of Groovy default args was to avoid having to do this - so I'm struggling to see what the point of them is.

@pidster
Copy link

pidster commented Apr 30, 2013

Two methods are required:

void deployModule(String moduleName, Map config = [:], int instances = 1) {
}

void deployModule(String moduleName, Map config = [:], int instances = 1, Closure handler) {
}

Then Groovy generates these 6:

void deployModule(String moduleName) {}
void deployModule(String moduleName, Map config) {}
void deployModule(String moduleName, Map config, int instances) {}
void deployModule(String moduleName, Closure handler) {}
void deployModule(String moduleName, Map config, Closure handler) {}
void deployModule(String moduleName, Map config, int instances, Closure handler) {}

@fix
Copy link

fix commented Apr 30, 2013

Sorry, it is colliding.

the true method generation (IMO, to be confirmed):

 void deployModule(String moduleName, Map config = [:], int instances = 1, Closure doneHandle)
 void deployModule(String moduleName, Map config = [:], Closure doneHandle)
 void deployModule(String moduleName, Closure doneHandle)

@purplefox
Copy link
Author

That makes no sense to me.

Why would Groovy generate methods that all take have the doneHandler parameter in there?

This would imply I always have to specify a doneHandler, which is not the case.

doneHandler should be optional.

@fix
Copy link

fix commented Apr 30, 2013

if you don't default parameter, it is kept in the signature, that makes sense to me

@pidster
Copy link

pidster commented Apr 30, 2013

See my answer.

@fix
Copy link

fix commented Apr 30, 2013

with the following

void deployModule(String moduleName, Map config = [:], int instances = 1, Closure doneHandle={})

it should also generates

void deployModule(String moduleName, Map config = [:], int instances = 1)
void deployModule(String moduleName, Map config = [:])
void deployModule(String moduleName)

@fix
Copy link

fix commented Apr 30, 2013

at some point default=optional, right?

@purplefox
Copy link
Author

The parameter DOES have a default - see original gist

@fix
Copy link

fix commented Apr 30, 2013

@pidster right it is, well done!

@purplefox
Copy link
Author

So to recap, here is the signature:

void deployModule(String moduleName, Map config = [:], int instances = 1, Closure doneHandle={})

Notice that ALL parameters have a default.

I would therefore expect Groovy to generate:

void deployModule(String moduleName, Map config, int instances, Closure doneHandler)
void deployModule(String moduleName, Map config, int instances)
void deployModule(String moduleName, Map config)
void deployModule(String moduleName)

None of which collide with

void deployModule(String moduleName, Closure doneHandler)

So I still don't see why there is a collision

@purplefox
Copy link
Author

It seems to me that Groovy is treating Closure parameters differently to other params?

@purplefox
Copy link
Author

I can see that pidsters solution does work. But it seems an ugly solution.

I can't grok why Groovy requires this workaround and can't figure it out itself.

@fix
Copy link

fix commented Apr 30, 2013

ok right @purplefox nailed it down, sounds a bug

@fix
Copy link

fix commented Apr 30, 2013

I think this is a design choice because there could be inconsistency with another case: the fact that two optional arguments can have the same type (for instance 2 Closure), and it would be impossible to generate the same way a signature

@fix
Copy link

fix commented Apr 30, 2013

@pidster @purplefox @robfletcher the bottom line is that if you overload a default, you SHOULD overload all default parameters declared BEFORE in the signature.

and by the way this is the same if the arg is not a Closure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment