web-dev-qa-db-ja.com

名前付きパラメーター

方法がある

def test(String a, String b) { }

ダイナミックパラメータマップを使用してこれを呼び出したいと思います。私はいつもそれでも

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

そしてまた

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

働くでしょう。しかし、そうではありません。 スプレッド演算子 を思い出しましたが、動作させることができません。..

単一のパラメーターではなく、パラメーターとして何らかのマップを使用して上記のようなメソッドを呼び出す方法はありますか?

43
rdmueller

たぶん何かを見逃したかもしれませんが、Groovyが現在パラメーターに名前を付けているとは思いません。 discussionsproposals がありますが、公式なものは知りません。

あなたの場合、マップの広がりmayが役立つと思いますが、すべての場合に当てはまるわけではありません。値を取得すると、マップ値が宣言された順序に従います。

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 :-(

クラスの場合、 Groovy's as operator

@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

メソッド呼び出しは、test(a:'1', b:'2');の代わりにtest([a:'1',b:'2']);であるべきではありませんか?

名前付きパラメーターここ を確認してください。

30
Tom

名前付きパラメーターのサポートは非​​常に柔軟ですが、ドキュメントは少し薄いです。ここに私が発見したいくつかのルールがあります。パラメータ(メソッドで宣言)と引数(メソッド呼び出しに渡される)のユーザーに曖昧さをなくそうとしていることに注意してください。

  • 最初にMapパラメーターを最初に宣言する必要があります。これは大きなものです。そして明らかではありません。
  • Argsに完全なMapは必要ありません。Map要素、つまり(a: "aa")で十分です。必要ありません([a: "aa"])
  • 順序付けられた引数が入力するパラメーターと同じ順序である限り、順序付けられた(名前のない)引数と名前の引数を混在させることができます
  • 名前付き引数と規則正しい順序付き引数を混在させることができます。これはかなりクールですが、やはり、順序付けられた引数は順序どおりでなければなりません。
  • 同じメソッドシグネチャでオプションの順序付きパラメータを使用することもできます(以下の例のxを参照)
  • Mapパラメーターにデフォルトの空のマップargs=[:]名前付き引数をオプションにしますが、他のオプションパラメータがある場合はうまく機能しません(以下の最後の例を参照)

以下に例を示します。paramsを入力する必要はありませんが、明確にするために型を追加しました。

// 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

Will P のコメントに感謝します。私の問題に合う解決策を見つけました。

型なしで1つのパラメーターを定義すると、hashMapを含むすべての種類の型を渡すことができます。そして、groovyはa:'h',b:'i'自動的にハッシュマップに

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

この方法では、単一の名前付きパラメーターを使用できますが、マップも使用できます!

5
rdmueller

Groovyが位置引数と名前付き引数/デフォルト引数をどのように行うかは絶対に嫌です。それはひどいです。 Pythonは間違いなく正しいことをします。

問題点

  1. 引数名を指定して関数を呼び出すと、実際にマップが作成され、そのマップが最初の引数になります。

コード

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")

これは実際に位置引数の順序を変更するため、悪いです。

  1. 通常のデフォルト引数は順番どおりに呼び出すことはできません

コード

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

もちろん、いつでも関数をオーバーライドして、引数を目的の位置に一致させることができます。これは、多くの議論があるときの大きな面倒です。

  1. Mapを最初の引数として使用すると、純粋な位置引数は許可されません

コード

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?

私の解決策...

最終的に、私の解決策は、任意の数の引数をオブジェクトとして受け取り、それらの引数を定義済みの引数のマップにマップすることでした。

コード

// 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)

私はこの解決策にはあまり満足していませんが、キーワード引数が私のニーズに合ったものになります。

1
justengel