Optional function arguments with no default value possible?

大兔子大兔子 提交于 2019-12-24 06:49:05

问题


In Chapel, we can set the default value of function formal arguments easily, for example,

proc test( a = 1, b = 2.0, c = "hi" ) {
    ...
}

and call the function by using keywords also:

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"

Here, I am wondering if it is possible to define a keyword argument that does not require a predefined default value. More specifically, I would like to write a function that can optionally receive an array depending on cases (e.g., to save intermediate data). Here, the only requirement is that I can check whether the actual argument is passed or not, and there is no need to give the default array value. I imagined something like

proc test( ..., optional d: [] real ) {
    if present( d ) then ...;
}

or

proc test( ..., d: [] real = None ) {
    if present( d ) then ...;
}

but was not able to find similar things. At the moment, my workaround is to give some dummy default value and check their properties to determine whether an actual argument is passed.

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
}

However, I am wondering whether there might be a more elegant(?) or idiomatic(?) approach...

Edit

As suggested in the comment, it is also convenient to overload several functions to get different interfaces, but at some point I guess I need to pass some "dummy" object to the final (full-fledged) routine and ask the latter to see if the passed object is "dummy" or not... MWE is something like this:

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

which gives

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

The corresponding default-value version is

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

which gives

test() with 2 args
(1.0, )

test() with 2 args
(1.0, 1 2 3 4 5)
got a non-empty array

Although the above approach works for arrays, the rule needs to change depending on the type of objects used. So, I was wondering if there is some systematic way, e.g., to pass a "null pointer" or some unique global object to tell the final routine about the presence of the actual data. (But, as noted above, the above approach works for arrays).

Edit 2

Another approach may be simply to pass an additional flag for using the passed array (then there is no need to think much about the nature of the default object, so may be overall simpler...)

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

Edit 3

Following Option 3 in the answer, here is a modified version of my code using _void and 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 );

Result:

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:


This answer discusses 3 answers:

  1. The strategy discussed in the edit of the question.
  2. A strategy using a Box type
  3. A strategy using a generic function with a void default value

My favorite of these options is Option 3.

Option 1

proc test( x: real, arr: [] int = empty1Dint, use_arr = false ) strategy described in the question is reasonable, if a little verbose. The main drawback here is that you'd need more overloads of test if you didn't want the call sites to have to pass use_arr=true or use_arr=false. Here is a simple program that does that:

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]);

Option 2

Another alternative is to create a class to store the optional argument data, and pass nil by default.

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()));

Here the main tricky part is that the array type returned by makeArray() and emptyBox() have to match. It'd be possible to use a type alias to have them refer to the same array type, but how exactly that would fit in depends on your application. Another problem with this approach is that it causes the array to be copied in the process of passing such an argument. And, one has to think about where the Box will be destroyed. Is test to hang on to the array value (e.g. storing it in a data structure) or just going to use it temporarily? This is set by the type returned by emptyBox in my example.

It's probably reasonable for the standard library to gain such a Box type but it doesn't have one now.

Option 3

My favorite solution to this problem is a third strategy altogether. Chapel includes a value of void type called _void. The key is the declaration proc test( optional:?t=_void ). Here test is a generic function - the syntax argument:?t indicates that the argument can have a varied type (which will be available as t within the function). This is necessary to get a generic argument that also has a default value (otherwise the argument will have only the type inferred from the default value).

If no optional argument is provided, it will instantiate with optional having type void. Which makes sense as a way to not pass something. Technically it's not the same as checking if the default value was provided, but I think a call site like test(optional=_void) is reasonably clear at communicating that the value of optional should be ignored (since it's void).

Anyway here is the code:

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]);


来源:https://stackoverflow.com/questions/53752817/optional-function-arguments-with-no-default-value-possible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!