Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy named parameters cause parameter assignments to switch--any way around this?

Groovy will collect all the named parameters into a map and pass it into a method as the first parameter. This seems neat, but after trying to make it work it seems really unusable.

So the issue is a method like this:

def method(paramMap, specificVar1 = 7, specificVar2 = 14)

when you call this method with something like this:

method(12, extraValue: "hello")

you get pretty much what you expect:

assert 12 == specificVar1
assert 14 == specificVar2
assert [extraValue:"hello"] == paramMap

not bad, makes sense. The problem is, if you assume the Map params are optional then you can end up with values like this:

method(12)
assert paramMap == 12
assert specificVar1 == 7 // default values
assert specificVar2 == 14

That scalar should have gone into specificVar--not the map. If I specifically type the map in the method:

def method(Map paramMap, specificVar1 = 7, specificVar2 = 14)

then method(12, extraValue: "hello") works just as it did before, but method(12) throws a ClassCastException. This just doesn't seem usable. Is there any way to make that Map "sticky" so that it will simply be empty if there are no Map parameters?

like image 939
Bill K Avatar asked Mar 13 '13 18:03

Bill K


People also ask

Does Groovy have named parameters?

Groovy collects all named parameters and puts them in a Map. The Map is passed on to the method as the first argument.

What is constructor in Groovy?

Since the early days of Groovy we can create POGO (Plain Old Groovy Objects) classes that will have a constructor with a Map argument. Groovy adds the constructor automatically in the generated class. We can use named arguments to create an instance of a POGO, because of the Map argument constructor.

How do you call a method in Groovy?

In Groovy, we can add a method named call to a class and then invoke the method without using the name call . We would simply just type the parentheses and optional arguments on an object instance. Groovy calls this the call operator: () . This can be especially useful in for example a DSL written with Groovy.

What is Groovy Param?

In Groovy we can assign default values to parameters in a method. If we define a default value for a parameter Groovy basically supports two method signatures: one with all parameters and one where the parameter with a default value is omitted.


1 Answers

Setting default values on parameters create overloaded methods with combinations made from left to right, thus, it is hard to make method(12) and also be able to pass map entries.

Your method def method(paramMap, specificVar1=7, specificVar2=14) will generate the following methods:

Object Maps.method(java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object,java.lang.Object)

And a fully typed method with the map param:

def method3(Map paramMap=[:], Integer specificVar1=7, Integer specificVar2=14) {
}

Will generate the following methods:

Object Maps.method3()
Object Maps.method3(java.util.Map)
Object Maps.method3(java.util.Map,java.lang.Integer)
Object Maps.method3(java.util.Map,java.lang.Integer,java.lang.Integer)

(No suitable method for method(12)).

Also, entries passed to the method will be collected and inserted in the first map parameter. The following method:

def method4(Integer specificVar1=7, Integer specificVar2=14, Map map=[:]) {

Generates:

Object Maps.method4()
Object Maps.method4(java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer,java.util.Map)

Thus, method4 12, a:'b' fails with:

No signature of method: Maps.method4() is applicable for argument types: 
  (java.util.LinkedHashMap, java.lang.Integer) values: [[a:b], 12]

So, no, i don't think you can do what you want using maps :-).


Solution 1:

If you are in for a pure dynamic solution, you can use a single map argument:

def method5(Map map) {
  def specificVar1 = map.specificVar1 ?: 7
  def specificVar2 = map.specificVar2 ?: 14
}

Solution 2 (updated):

You can create a class to represent the parameters. Using a map to be coerced into the object is statically compilable and a syntatic sugar for it.

@groovy.transform.CompileStatic
class Maps {
  def method6(Foo foo) { "$foo.params, $foo.specificVar1, $foo.specificVar2" }
  def method6(Map map) { method6 map as Foo }

  static main(args) {
    def maps = new Maps()

    assert maps.method6(params: [a: 'b', c: 'd'], specificVar1: 40) ==
        "[a:b, c:d], 40, 14"

    assert maps.method6(new Foo(params: [a: 'b', c: 'd'], specificVar2: 21)) == 
        "[a:b, c:d], 7, 21"
  }
}

class Foo {
  def specificVar1 = 7, specificVar2 = 14, params = [:]
}

Solution 3:

An overloaded method.

def method6(Map paramMap, Integer specificVar1=7, Integer specificVar2=14) {
  "$paramMap, $specificVar1, $specificVar2"
}

def method6(Integer specificVar1=7, Integer specificVar2=14) {
  method6 [:], specificVar1, specificVar2
}


assert method6( 12 ) == "[:], 12, 14"
assert method6( ) == "[:], 7, 14"
assert method6( a:'b', 18 ) == "[a:b], 18, 14"
assert method6( 18, a:'b', 27 ) == "[a:b], 18, 27"
assert method6( 90, 100 ) == "[:], 90, 100"
assert method6( a:'b', 140, c:'d' ) == "[a:b, c:d], 140, 14"

The map version method can't have a default parameter, otherwise both methods will generate a parameterless method6 and those will conflict.

like image 69
Will Avatar answered Nov 02 '22 22:11

Will