Именованные параметры
У меня есть метод
def test(String a, String b) { }
и я хотел бы назвать это с помощью карты динамических параметров.
Я всегда, хотя
test(['1','2']); //valid call
а также
test([a:'1',b:'2']); //=> does not work
будет работать. но это не так. Поэтому я вспомнил оператор распространения, но не смог заставить его работать.
Есть ли способ вызвать метод, подобный приведенному выше, с каким-либо видом карты в качестве параметра вместо одиночных параметров?
Ответы
Ответ 1
Возможно, я что-то пропустил, но я не думаю, что Groovy назвал параметры прямо сейчас. Есть дискуссии и предложения, но я не знаю ничего официального.
Для вашего случая, я думаю, распространение карты может помочь, но не в каждом случае. Получив значения, следует порядок, в котором объявлены значения карты:
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 в качестве оператора?
@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"
}
}
Ответ 2
Не следует ли test(a:'1', b:'2');
вызов метода test(a:'1', b:'2');
вместо test([a:'1',b:'2']);
?
Пожалуйста, проверьте здесь именованные параметры.
Ответ 3
Поддержка названных параметров довольно гибкая, но документы немного тонкие. Вот некоторые из правил, которые я обнаружил. Обратите внимание, что я пытаюсь быть однозначным в пользователе параметров (объявленных в методе) и args (переданных вызову метода)
- Сначала должен быть объявлен параметр "Карта". Это большой. И не очевидно.
- Вам не нужна полная карта в ваших аргументах, просто элементы карты, т.е.
(a: "aa")
достаточно хороши, вам не нужно ([a: "aa"])
- Вы можете смешать упорядоченные (неназванные) аргументы с именами args, если упорядоченные аргументы остаются в том же порядке, что и параметры, которые они заполняют
- Вы можете пересечь именные args с регулярными упорядоченными аргументами. Это довольно круто, но опять же упорядоченные аргументы должны быть в порядке, ну, порядок.
- Вы даже можете использовать необязательные упорядоченные параметры в одной и той же сигнатуре метода (см.
x
в приведенных ниже примерах) - Вы можете присвоить параметру Map пустую карту по умолчанию
args=[:]
сделав названные args необязательными, но это не сработает, если у вас есть другие необязательные параметры (см. Приведенные ниже примеры)
Вот несколько примеров: параметры не нужно вводить, но я добавил типы для ясности.
// 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)
Ответ 4
thanx в комментарии Will P, я нашел решение, которое соответствует моей проблеме:
Если я определяю один параметр без типа, я могу передавать все типы типов, включая hashMaps. И 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
Я абсолютно ненавижу, как Groovy делает позиционные и именованные /default аргументы. Это ужасно Python делает это правильно, без вопросов.
Проблемы
- Вызов функции с именами аргументов фактически создает карту и делает эту карту первым аргументом.
код
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")
Это плохо, потому что это фактически меняет порядок позиционных аргументов.
- Обычные аргументы по умолчанию не могут быть вызваны не по порядку
код
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
Конечно, вы всегда можете переопределить функцию, чтобы аргументы соответствовали желаемой позиции. Это просто огромная проблема, когда у вас много аргументов.
- Использование карты в качестве первого аргумента не допускает чисто позиционные аргументы
код
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)
Я не очень доволен этим решением, но оно заставляет ключевые аргументы работать для моих нужд.
Ответ 6
Этот вопрос заставил меня задуматься, и я нашел гибкое, интересное (если не страшное) решение.
Кажется, эта подпись принимает абсолютно любую комбинацию аргументов:
f(Map m=null, Object... obj)
и делает с ними в основном предсказуемые вещи.
- Если вы передаете какие-либо именованные аргументы вообще (в любых позициях), они переходят в m
- Если вы передаете позиционные аргументы, они переходят в obj в правильном порядке.
- Если вы передаете карту, она считается позиционным аргументом
- Если у вас нет именованных параметров, m равно нулю (если у вас нет карты в качестве первого параметра, см. ниже)
Единственная раздражающая странность - это случай, когда вы передаете карту в качестве первого позиционного параметра.
f([a:1], 2) has a map m[a:1] and one obj[0]=2
однако
f([a:1], b:2)
имеет карту m из [b: 2] и объект obj [0] = карту [a: 1], поэтому вы не сможете точно сказать, была ли карта m позиционной картой или названным параметром.
Кстати, я не обязательно рекомендую что-либо из этого, я бы использовал точные параметры всякий раз, когда это возможно (и я даже предпочитаю явные типы). В моем собственном коде довольно много раз, когда я немного запутывался и исправлял вещи, добавляя некоторую явную типизацию, однако в некоторых случаях это может быть весьма полезно.