functionargumentschapel

Making a `writeln` like function with an arbitrary number of arguments


writeln() is useful for printing variables, but one inconvenience (to me) is that it does not insert spaces between variables (unlike print() in Python). For example,

var x = 100, y = 200;
writeln(x, y);                   // 100200
writeln("x, y =", x, y);         // x, y =100200
writeln("x, y = ", x, " ", y);   // x, y = 100 200 (need manual spaces)

One can enclose all the arguments in parentheses (to make them a tuple)

writeln(("x, y =", x, y));       // (x, y =, 100, 200)

but the output becomes a bit awkward because of additional commas (depending on the data to be printed).

So, I am wondering if it is possible to make a custom writeln() like function that inserts spaces between all the arguments. For this purpose, is there some method for writing a function that receives an arbitrary number of arguments? (As a workaround, I could overload different versions of "myprint()", for example, but I guess there may be a different approach.)

proc myprint(x1) { writeln(x1); }
proc myprint(x1, x2) { writeln(x1, " ", x2); }

myprint("hello");            // hello
myprint("hello", "world");   // hello world

Solution

  • Yes, Chapel supports routines that accept a variable number of arguments (varargs or variadic arguments), so it is possible to write such routines yourself. I'll also note that you're not alone in your request for a routine that inserts spaces between arguments, as noted in issues like this and this.

    As a simple example, the following procedure accepts an arbitrary number of arguments of arbitrary type and prints them out with spaces between each [try it on ATO]:

    config const debug = true;
    
    proc myPrintln(args...) {
      if debug then writeln("called with: ", args.type:string);
      for arg in args do
        write(arg, " ");
      writeln();
    }
    
    myPrintln(1, 2, 3);
    myPrintln(1, 2.3, "four");
    

    In such routines, the single formal argument identifier representing the variable number of arguments represents all of the arguments as a tuple type. So here, whether I pass in 3 ints or an int, real, and string, args will be a tuple representing those arguments, as indicated by the debug print at the routine's start. I then iterate over those arguments, printing them out one at a time.

    Here's a second variation where I loop over the tuple's indices in order to detect the last element and squash the extra trailing space [ATO]:

    config const debug = false;
    
    proc myPrintln(args...) {
      if debug then writeln("called with: ", args.type:string);
      for param i in 0..<args.size do
        write(args[i], if i == args.size-1 then "" else " ");
      writeln();
    }
    
    myPrintln(1, 2, 3);
    myPrintln(1, 2.3, "four");
    

    Additional decorators can be attached to formal varargs arguments to constrain them to be a specific type (e.g., "a variable number of integers") or to query things like the number of arguments passed to the varargs routine. See this primer or this section in the language specification for more details.