Необязательные аргументы функции без значения по умолчанию возможно?
В Chapel мы можем легко установить значение по умолчанию для формальных аргументов функции, например:
proc test( a = 1, b = 2.0, c = "hi" ) {
...
}
и вызвать функцию, используя также ключевые слова:
test( 10 ); // a = 10, b = 2.0, c = "hi"
test( b = 3.14 ); // a = 1, b = 3.14, c = "hi"
test( c = "yo" ); // a = 1, b = 2.0, c = "yo"
Здесь мне интересно, возможно ли определить ключевое слово аргумента, которое не требует предопределенного значения по умолчанию. Более конкретно, я хотел бы написать функцию, которая может дополнительно принимать массив в зависимости от случаев (например, для сохранения промежуточных данных). Здесь единственным требованием является то, что я могу проверить, передан ли фактический аргумент или нет, и нет необходимости указывать значение массива по умолчанию. Я представлял что-то вроде
proc test( ..., optional d: [] real ) {
if present( d ) then ...;
}
or
proc test( ..., d: [] real = None ) {
if present( d ) then ...;
}
но не смог найти похожие вещи. На данный момент мой обходной путь - дать какое-то фиктивное значение по умолчанию и проверить их свойства, чтобы определить, передан ли фактический аргумент.
proc test( arr = empty2Dreal ) { ... } // where "empty2Dreal" is a pre-defined global array
or
proc test( arr = reshape( [0.0], {1..1,1..1} ) ) { ... } // some dummy array
}
Однако мне интересно, может ли быть более элегантный (?) Или идиоматический (?) Подход...
редактировать
Как предлагается в комментарии, также удобно перегружать несколько функций для получения различных интерфейсов, но в какой-то момент я предполагаю, что мне нужно передать какой-то "фиктивный" объект в конечную (полноценную) подпрограмму и попросить последнюю посмотреть, если переданный объект "фиктивный" или нет... MWE выглядит примерно так:
const empty1Dint: [1..0] int;
proc test( x: real, arr: [] int )
{
writeln("test() with 2 args");
writeln(( x, arr ));
// here, I need to check whether the passed object is
// an actual array or not by some predefined rule
if arr.size > 0 then writeln("got a non-empty array");
}
proc test( x: real )
{
writeln("test() with 1 arg");
test( x = x, arr = empty1Dint );
}
var work = [1,2,3,4,5];
test( x = 1.0 );
writeln();
test( x = 1.0, arr = work );
который дает
test() with 1 arg
test() with 2 args
(1.0, )
test() with 2 args
(1.0, 1 2 3 4 5)
got a non-empty array
Соответствующая версия по умолчанию
const empty1Dint: [1..0] int;
proc test( x: real, arr: [] int = empty1Dint )
{
writeln("test() with 2 args");
writeln(( x, arr ));
if arr.size > 0 then writeln("got a non-empty array");
}
var work = [1,2,3,4,5];
test( x = 1.0 );
writeln();
test( x = 1.0, arr = work );
который дает
test() with 2 args
(1.0, )
test() with 2 args
(1.0, 1 2 3 4 5)
got a non-empty array
Хотя вышеуказанный подход работает для массивов, правило должно меняться в зависимости от типа используемых объектов. Итак, мне было интересно, существует ли какой-то систематический способ, например, передать "нулевой указатель" или какой-то уникальный глобальный объект, чтобы сообщить конечной процедуре о наличии фактических данных. (Но, как отмечено выше, вышеуказанный подход работает для массивов).
Редактировать 2
Другой подход может состоять в том, чтобы просто передать дополнительный флаг для использования переданного массива (тогда нет необходимости много думать о природе объекта по умолчанию, поэтому может быть в целом проще...)
const empty1Dint: [1..0] int;
proc test( x: real, arr: [] int = empty1Dint, use_arr = false )
{
writeln( "x= ", x );
if use_arr {
writeln("working with the passed array...");
for i in 1..arr.size do arr[ i ] = i * 10;
}
}
test( x = 1.0 );
writeln();
var work: [1..5] int;
test( x = 2.0, arr = work, use_arr = true );
writeln( "work = ", work );
Редактировать 3
После варианта 3 в ответе, здесь измененная версия моего кода с использованием _void
а также void
:
proc test( x: real, arr: ?T = _void )
{
writeln( "\ntest():" );
writeln( "x = ", x );
writeln( "arr = ", arr );
writeln( "arr.type = ", arr.type:string );
writeln( "T = ", T:string );
if arr.type != void {
writeln( "doing some checks" );
assert( isArray( arr ) );
}
if arr.type != void {
writeln( "writing arr" );
for i in 1..arr.size do arr[ i ] = i * 10;
}
}
// no optional arg
test( x = 1.0 );
// use an optional arg
var work: [1..5] int;
test( x = 2.0, arr = work );
writeln( "\nmain> work = ", work );
Результат:
test():
x = 1.0
arr =
arr.type = void
T = void
test():
x = 2.0
arr = 0 0 0 0 0
arr.type = [domain(1,int(64),false)] int(64)
T = [domain(1,int(64),false)] int(64)
doing some checks
writing arr
main> work = 10 20 30 40 50
1 ответ
Этот ответ обсуждает 3 ответа:
- Стратегия обсуждается в редакции вопроса.
- Стратегия с использованием
Box
тип - Стратегия, использующая обобщенную функцию со значением void по умолчанию
Мой любимый из этих вариантов - вариант 3.
Опция 1
proc test( x: real, arr: [] int = empty1Dint, use_arr = false )
Стратегия, описанная в вопросе, разумна, если немного многословна. Основным недостатком здесь является то, что вам нужно больше перегрузок test
если вы не хотите, чтобы сайты вызовов проходили use_arr=true
или же use_arr=false
, Вот простая программа, которая делает это:
proc test(optional, hasOptional:bool) {
writeln("in test");
writeln(" optional is ", optional);
if hasOptional == false then
writeln(" note: default was used for optional");
}
proc test(optional) {
test(optional, hasOptional=true);
}
proc test() {
var emptyArray:[1..0] int;
test(emptyArray, hasOptional=false);
}
test();
test([1, 2, 3]);
Вариант 2
Другой альтернативой является создание класса для хранения данных необязательного аргумента и передачи nil по умолчанию.
class Box {
var contents;
}
proc makeArray() {
var A:[1..2] int;
return A;
}
proc emptyBox() {
var A:[1..0] int;
var ret: owned Box(A.type) = nil;
return ret;
}
proc test( optional=emptyBox() ) {
writeln("in test with optional=", optional);
}
test();
test(new owned Box(makeArray()));
Здесь основная сложность заключается в том, что тип массива, возвращаемый makeArray() и emptyBox(), должен совпадать. Можно было бы использовать псевдоним типа, чтобы они ссылались на один и тот же тип массива, но как именно это будет соответствовать, зависит от вашего приложения. Другая проблема этого подхода состоит в том, что он вызывает копирование массива в процессе передачи такого аргумента. И нужно подумать о том, где Box
будет уничтожен Является test
привязаться к значению массива (например, сохранить его в структуре данных) или просто использовать его временно? Это устанавливается типом, возвращаемым emptyBox
в моем примере.
Для стандартной библиотеки, вероятно, разумно получить такой Box
типа, но сейчас его нет.
Вариант 3
Мое любимое решение этой проблемы - третья стратегия. Часовня включает в себя значение типа void, называемое _void. Ключ декларации proc test( optional:?t=_void )
, Вот test
является универсальной функцией - синтаксис argument:?t
указывает, что аргумент может иметь различный тип (который будет доступен как t
в рамках функции). Это необходимо для получения универсального аргумента, который также имеет значение по умолчанию (в противном случае аргумент будет иметь только тип, выведенный из значения по умолчанию).
Если нет optional
аргумент предоставляется, он будет создан с optional
имеющий тип void
, Что имеет смысл как способ не пропустить что-то. Технически это не то же самое, что проверка, было ли предоставлено значение по умолчанию, но я думаю, что сайт вызова test(optional=_void)
достаточно ясно сообщая, что ценность optional
следует игнорировать (так как это void
).
В любом случае вот код:
proc test( optional:?t=_void ) {
writeln("in test");
writeln(" optional is ", optional);
if optional.type == void then
writeln(" note: default was used for optional");
}
test();
test([1, 2, 3]);