web-dev-qa-db-fra.com

Paramètres nommés

J'ai de la méthode

def test(String a, String b) { }

et je voudrais appeler cela avec une carte de paramètres dynamique. J'ai toujours pensé que

test(['1','2']); //valid call

et aussi

test([a:'1',b:'2']); //=> does not work

marchera. mais ce n'est pas le cas. Je me suis donc souvenu l'opérateur de propagation , mais je ne peux pas le faire fonctionner ....

Existe-t-il un moyen d'appeler une méthode comme celle ci-dessus avec une sorte de carte en paramètre au lieu de paramètres uniques?

43
rdmueller

Peut-être que j'ai raté quelque chose, mais je ne pense pas que Groovy ait nommé des paramètres pour le moment. Il y a discussions et propositions , mais je ne suis au courant de rien d'officiel.

Pour votre cas, je pense que la propagation de la carte peut aider, mais pas dans tous les cas. Lors de l'obtention des valeurs, il suit l'ordre dans lequel les valeurs de la carte ont été déclarées:

def test(String a, String b) { "a=$a, b=$b" }
def test(Map m) { test m*.value }

assert test(a: "aa", b:"bb") == "a=aa, b=bb"
assert test(b: "aa", a:"bb") != "a=aa, b=bb" // should be false :-(
assert test(b: "ccc", a:"ddd") == "a=ddd, b=ccc" // should have worked :-(

Pour les cours, puis-je suggérer Groovy's comme opérateur ?

@groovy.transform.CompileStatic
class Spread {
  class Person {
    String name
    BigDecimal height
  }

  def method(Person p) {
    "Name: ${p.name}, height: ${p.height}"
  }

  def method(Map m) { method m as Person }

  static main(String[] args) {
    assert new Spread().method(name: "John", height: 1.80) == 
      "Name: John, height: 1.80"
  }
}
27
Will

L'appel de méthode ne devrait-il pas être test(a:'1', b:'2'); au lieu de test([a:'1',b:'2']);?

Veuillez vérifier Paramètres nommés ici .

30
Tom

Le support des paramètres nommés est assez flexible, mais les documents sont un peu fins. Voici quelques règles que j'ai découvertes. Notez que j'essaie d'être sans ambiguïté dans l'utilisateur des paramètres (déclarés dans la méthode) et des arguments (passés à l'appel de méthode)

  • Le paramètre Map doit d'abord être déclaré. C'est le gros. Et pas évident.
  • Vous n'avez pas besoin d'une carte complète dans vos arguments, juste des éléments de carte, c'est-à-dire (a: "aa") est assez bon, vous n'avez pas besoin de ([a: "aa"])
  • Vous pouvez mélanger des arguments ordonnés (sans nom) avec des arguments de nom, tant que les arguments ordonnés restent dans le même ordre que les paramètres qu'ils remplissent
  • Vous pouvez intercaler des arguments nommés avec des arguments ordonnés réguliers. C'est assez cool, mais encore une fois, les arguments ordonnés doivent être dans l'ordre.
  • Vous pouvez même utiliser des paramètres ordonnés facultatifs dans la même signature de méthode (voir x dans les exemples ci-dessous)
  • Vous pouvez donner au paramètre Map une carte vide par défaut args=[:] rendant les arguments nommés facultatifs, mais cela ne fonctionne pas bien si vous avez d'autres paramètres facultatifs (voir les derniers exemples ci-dessous)

Voici quelques exemples: Les paramètres n'ont pas besoin d'être saisis, mais j'ai ajouté des types pour plus de clarté.

// this method has a map args to capture all named args
// and non-named (ordered) args String s, int n, and int x
// x has a default value so is optional
// the map (here untyped) to capture the nameed args MUST COME FIRST
def m(Map args=[:], String s, int n, int x=1)
{
    println "s:$s n:$n x:$x, args:$args"
}

//1: pass in named args first, then ordered
m(a: "aa", b: 3, "ss", 44, 5) // s:s n:44 x:5, args:[a:aa, b:3]

//2:  ordered args first -  named args last (same result)
m("ss", 44, 5, a: "aa", b: 3) // s:s n:44 x:5, args:[a:aa, b:3]

//3:  bring the first ordered arg (s) to the start (same result)
m("ss", a: "aa", b: 3, 44, 5) // s:s n:44 x:5, args:[a:aa, b:3]

//4: stick the ordered arg n in the middle of the named args (same result!)
m("ss", a: "aa", 44, b: 3, 5) // s:s n:44 x:5, args:[a:aa, b:3]


//5:  mix the ordered args in with the named and SKIP the arg x with default value (x=1)
m(a: "aa", "ss", b: 3, 44) // s:ss n:44 x:1, args:[a:aa, b:3] 

//6: ordered arg n first - so in the wrong order (Fail!)
//m(44, "ss", a: "aa", b: 3, 5) //MissingMethodException: No signature .. of .. m() .. applicable for 
                             // argument types: (Java.util.LinkedHashMap, Java.lang.Integer, Java.lang.String, Java.lang.Integer)
                             // values: [[a:aa, b:3], 44, ss, 5]

//7:  no named args: Fails! (change signature to add default: Map args=[:] and it will succeed with: s:ss n:44 x:1, args:[:]
m("ss", 44) // ...No signature ... applicaple ... types (Java.lang.String, Java.lang.Integer)

//8: no named args: Fails! (even with default map in signature this fails!)
m("ss", 44, 5) // ...No signature ... applicaple ... types (Java.lang.String, Java.lang.Integer, Java.lang.Integer)
9
Rhubarb

merci au commentaire de Will P , j'ai trouvé une solution qui correspond à mon problème:

si je définis un paramètre sans type, je peux passer dans toutes sortes de types, y compris hashMaps. Et groovy transforme une construction comme a:'h',b:'i' automagiquement dans une table de hachage

def test(myParams, Integer i) {
    return myParams.a + myParams.b
}

assert test(a:'h',b:'i',5) == test(b:'i',a:'h',5)
assert test([a:'h',b:'i'],5) == test(b:'i',a:'h',5)
test('h','i',5); //still throws an exception

De cette façon, je peux utiliser des paramètres nommés uniques, mais je peux aussi utiliser une carte!

5
rdmueller

Je déteste absolument comment groovy fait des arguments positionnels et nommés/par défaut. C'est terrible. Python le fait bien sans question.

Problèmes

  1. L'appel d'une fonction avec des noms d'argument crée en fait une carte et fait de cette carte le premier argument.

code

test(a:"a", b: "b") // Actual myfunc([a: "a", b: "b"])
test("a", b: "b")  // Actual myfunc([b: "b"], "a")
test(a: "a", "b")  // Actual myfunc([a: "a"], "b")

C'est mauvais car cela change réellement l'ordre des arguments positionnels.

  1. Les arguments par défaut normaux ne peuvent pas être appelés dans le désordre

code

def test(String a, String b, int x=1, int y=2){
  a = args.get('a', a)
  b = args.get('b', b)
  x = args.get('x', x)
  y = args.get('y', y)

  println "a:$a b:$b x:$x, y:$y"
}

test("a", 'b')  // Positional arguments without giving the default values
// "a:a b:b x:1 y:2"

test("a", "b", 3)  // Positional arguments with giving 1 default and not the last
// "a:a b:b x:3 y:2"

test("a", "b", y:4)  // Positional with Keyword arguments. Actual call test([y:4], "a", "b")
// This fails!? No signature of method, because Map is the first argument

Bien sûr, vous pouvez toujours remplacer la fonction pour que les arguments correspondent à la position souhaitée. C'est juste un gros tracas quand vous avez beaucoup d'arguments.

  1. L'utilisation d'une carte comme premier argument ne permet pas les arguments de position purs

code

def test1(Map args=[:], String a, String b, int x=1, int y=2){
  a = args.get('a', a)
  b = args.get('b', b)
  x = args.get('x', x)
  y = args.get('y', y)

  println "test1(a:$a b:$b x:$x, y:$y, args:$args)"
}

test1("ss", "44", 5, c: "c", d: 3)  // Actual test2([c: "c", d: 3], "ss", "44", 5) Matches our definition
// test1(a:ss b:44 x:5, y:2, args:[c:c, d:3, a:ss, b:44, x:5, y:2])

test1(a: "aa", b: 3, "ss", "44", 5)  // Actual test2([a: "aa", b: 3], "ss", "44", 5) Nothing wrong with repeat parameters because they are in the map
// test1(a:aa b:3 x:5, y:2, args:[a:aa, b:3, x:5, y:2])

test1(a: "aa", b: 3, "ss", "44", y:5)  // Actual test2([a: "aa", b: 3, y:5], "ss", "44") y is in the map, so y still has the default positional value
// test1(a:aa b:3 x:1, y:5, args:[a:aa, b:3, y:5, x:1])

test1("ss", "44", y:3)  // Actual test2([y:3], "ss", "44")
// test1(a:ss b:44 x:1, y:3, args:[y:3, a:ss, b:44, x:1])

test1('a', 'b')  // Pure positional arguments only required arguments given (no defaults given)
// test1(a:a b:b x:1, y:2, args:[a:a, b:b, x:1, y:2])

test1("ss", "44", 5)  // Pure positional arguments one missing
// This fails!? No signature of method. Why?

test1("ss", "44", 5, 6)  // Pure positional arguments all arguments given
// This fails!? No signature of method. Why?

Ma solution ...

En fin de compte, ma solution était de prendre un nombre illimité d'arguments en tant qu'objets et de mapper ces arguments avec une carte d'arguments définie.

code

// Return a Map of arguments with default values. Error if argument is null
def mapArgs(Object args, Map m){
  Map check = [:]
  def offset = 0

  // Check if first argument is map and set values
  if (args[0] instanceof Map){
    check = args[0]
    offset += 1
    check.each{ subitem ->
      m[subitem.key] = subitem.value
    }
  }

  // Iter positional arguments. Do not replace mapped values as they are primary.
  m.eachWithIndex{ item, i ->
    m[item.key] = ((i + offset) < args.size() && !check.containsKey(item.key)) ? args[i + offset] : item.value
    if (m[item.key] == null){
      throw new IllegalArgumentException("Required positional argument ${item.key}")
    }
  }
  return m
}

def test2(Object... args) {
  // println "args $args"
  def m = mapArgs(args, [a: null, b: null, x: 1, y:2])
  println "test2(a:$m.a b:$m.b x:$m.x, y:$m.y, args:null)"
}

test2("ss", "44", 5, c: "c", d: 3)  // Actual test2([c: "c", d: 3], "ss", "44", 5) Matches our definition
// test1(a:ss b:44 x:5, y:2, args:[c:c, d:3, a:ss, b:44, x:5, y:2])
// test2(a:ss b:44 x:5, y:2, args:null)

test2(a: "aa", b: 3, "ss", "44", 5)  // Actual test2([a: "aa", b: 3], "ss", "44", 5) Nothing wrong with repeat parameters because they are in the map
// test1(a:aa b:3 x:5, y:2, args:[a:aa, b:3, x:5, y:2])
// test2(a:aa b:3 x:5, y:2, args:null)

test2(a: "aa", b: 3, "ss", "44", y:5)  // Actual test2([a: "aa", b: 3, y:5], "ss", "44") y is in the map, so y still has the default positional value
// test1(a:aa b:3 x:1, y:5, args:[a:aa, b:3, y:5, x:1])
// test2(a:aa b:3 x:1, y:5, args:null)

test2("ss", "44", y:3)  // Actual test2([y:3], "ss", "44")
// test1(a:ss b:44 x:1, y:3, args:[y:3, a:ss, b:44, x:1])
// test2(a:ss b:44 x:1, y:3, args:null)

test2('a', 'b')  // Pure positional arguments only required arguments given (no defaults given)
// test1(a:a b:b x:1, y:2, args:[a:a, b:b, x:1, y:2])
// test2(a:a b:b x:1, y:2, args:null)

test2("ss", "44", 5)  // Pure positional arguments one missing
// This fails!? No signature of method. Why?
// test2(a:ss b:44 x:5, y:2, args:null)

test2("ss", "44", 5, 6)  // Pure positional arguments all arguments given
// This fails!? No signature of method. Why?
// test2(a:ss b:44 x:5, y:6, args:null)

Je ne suis pas vraiment satisfait de cette solution, mais cela fait fonctionner les arguments de mots clés à mes besoins.

1
justengel