Arguments, Parameters and Placeholders
What is an argument?
An argument is a value that you provide as input of a function. Let's say you want to subtract the number five from eleven and hence write:
sub(11, 5)
Then 11 and 5 are the arguments passed to the sub function. 11, 5 is called the argument list. Let's look at another example:
sum(range(13, 19))
Here the arguments passed to the range function are obviously 13 and 19. But which arguments does the sum function receive? The arguments passed to sum are exactly the output of the range function, in this case the seven numbers 13, 14, 15, 16, 17, 18, 19.
How many arguments can a function receive?
There is no general limit on how many arguments can be passed to a function. Zero arguments are okay and so are thousands of arguments. However, many specific functions restrict how many arguments they accept. The sub function, for instance, accepts exactly two arguments.
You may also run into practical limits with functions that accept arbitrarily many arguments in theory. That is because our computers have finite memory and we expect to see the result of a computation after a finite amount of time.
The call stack is a special kind of memory which is quite limited. It is not unlikely to exceed it with a few thousand arguments, if the function processes its arguments with single recursion. You may see an error message such as
# Sorry! JavaScript InternalError 'too much recursion'.
or
# Sorry! JavaScript RangeError 'Maximum call stack size exceeded.'.
in that case.
What is a parameter?
Parameters specify the number and kinds of arguments a function accepts. Let's look at a simple example:
decrement(Int) = sub($a, 1);
This defines a function decrement which expects exactly one integer argument, subtracts 1 from it, and returns the result. The parameter is to be found between the parentheses after the function name on the left side of the = sign: Int. This type represents an integer. Now that the function is defined, you could call it with the integer argument 7 for example, decrement(7), and you would get the number 6 in return.
Here is another example:
swap(?, ?) = $b, $a;
This defines a function swap which expects exactly two arguments that can be of any type. The function returns them in reverse order. The parameter list is ?, ? in this case: two wildcards which accept one arbitrary item each. You could call the function as swap("right", "left") and would get "left", "right" as response.
It is typical for parameters to be a type as in the first example or a wildcard as in the second example. The former narrows accepted arguments down while the latter accepts any kind of argument. But it is also possible to use specific values as parameter. Here is an example:
answer_me("please") = "Sure, honey!";
This defines the function answer_me for a single, specific parameter "please". A specific parameter matches only an identical argument. You can call the function as answer_me("please") and will get "Sure, honey!" as response. But calling the function with any other argument will not work unless the function's definition is extended with further mappings.
What is a placeholder?
In Quirl, parameters aren't named by the programmer. They are automatically tied to a placeholder marked by a dollar sign and a small Latin letter instead. The first parameter is tied to the placeholder $a, the second parameter to the placeholder $b, the third to $c and so forth. Let's look at the second example from the previous section again:
swap(?, ?) = $b, $a;
What's left of = says that this mapping defines a function swap which expects two arbitrary arguments. The right side defines what should be returned when the function is called: the list $b, $a wherein $a is replaced by the argument corresponding to the first parameter and $b is replaced by the argument corresponding to the second parameter.
It may seem unusual not to be able to name parameters, especially if you have been trained to give them descriptive names in other programming languages. Quirl focuses on mathematical computations though. It is common in math to use single-letter variable names. Quirl's consistent automatic naming scheme is simpler and more accessible than individually chosen symbols then.
In case you are unsure about the alphabet – the order is $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n, $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z.
How to convert arguments to the expected type?
Usually you don't have to, because the Quirl interpreter does it automatically. Automatic conversions can be and are always done along the following paths when necessary:
Int→Rat→Quad→Sqrum→FloatInt→Rat→PolTurn→Circ- any structure →
Tuple
Numeric conversions are sometimes possible in the opposite direction as well and are automatically performed then as needed:
Int⇠Rat⇠Quad⇠SqrumInt⇠Rat⇠Pol
Conversions are not possible when they don't make sense – like trying to convert the imaginary unit i or the square root of two \2 to a rational number. Conversions from imprecise types (Float, Circ) back to precise types aren't possible either.
Conversions to Text do not happen automatically, but you can convert anything to text manually with the text core function.
Anything can be wrapped in a tuple or set with [ ] and { } or using the tuple and set functions respectively. The peel core function on the other hand makes the content of tuples and sets available as list. Neither technique is a mere type conversion, but both ideas can be combined to convert a tuple into a set and vice versa: peel away what you don't need and wrap the resulting list anew.
How do union types work in parameter lists?
A union type parameter accepts an argument of either type. Let's look at the definition of an example function:
unity(Float+Modp) = same($a, mul($a, $a));
This function can be called with an argument of type Float as in unity(!1) or an argument of type Modp as in unity(8%7).
In case of a function with multiple union type parameters all combinations of the given types are possible, if the parameters differ. For example, the following definition of a function expects a tuple or set as first argument and a rational number or an angle that can be expressed as a fraction of a full turn as second argument:
containsrat(Set+Tuple, Rat+Turn) = in($b, {peel($a)});
The two parameters differ, so all these calls would be valid:
containsrat({5/6, "🐀", 1/4t}, 5/6); # argument types: Set, Rat
containsrat({5/6, "🐀", 1/4t}, 90°); # argument types: Set, Turn
containsrat([5/6, "🐀", 1/4t], 5/6); # argument types: Tuple, Rat
containsrat([5/6, "🐀", 1/4t], 90°); # argument types: Tuple, Turn
If multiple parameters represent the same union types, corresponding arguments must be of the same type though. Take the following function for example which returns whether the first argument exceeds the second argument in size:
exceeds(Set+Tuple, Set+Tuple) = dec(size($a), size($b));
It has two equal union types Set+Tuple as parameters. Hence the function can be called with two sets or with two tuples as arguments. The following calls are valid:
exceeds([x^2(i), neg(1)], ["♫♩"]); # argument types: Tuple, Tuple
exceeds({x^2(i), neg(1)}, {"♫♩"}); # argument types: Set, Set
But the function could not be called with a combination of one set and one tuple as arguments.
Can a parameter have a default value?
No and yes. You can not set a default value in a function's parameter list like in some other programming languages. But you can easily achieve the same effect. Have a look at the following code:
tasks_done(Int) = sum("Tasks finished today: " text($a) ".");
tasks_done() = tasks_done(0);
The first line defines a function tasks_done whose Int parameter requires exactly one integer argument. But there is another mapping in the second line where tasks_done has an empty parameter list. It maps tasks_done, called without argument, to tasks_done with the argument 0, effectively making 0 the default argument of the function.
How many parameters can a function have?
Twenty-six.
Why? Because each parameter is tied to a placeholder from the Latin alphabet with its 26 letters, so $a to $z are available. Twenty-six may seem like an arbitrary limit anyway and we could indeed implement more. But 26 is already far beyond general recommendations on how many parameters a function should have. Please file an issue if your application truly needs more.
What is a variadic parameter?
Quirl supports passing arbitrarily many arguments to a function, but a function can only
have twenty-six parameters – how does that compute? With variadic parameters! A variadic parameter can capture an arbitrary number of arguments: none, one or many. Variadic parameters are marked by three consecutive dots before a type or wildcard. For example, this is how a function that computes the average of a bunch of rational numbers could be defined:
arithmetic_mean(Rat ...Rat) = div(sum($a $b) count($a $b));
Its first parameter Rat expects one rational argument while its variadic second parameter ...Rat accepts any number of rational arguments. Together, they make the function arithmetic_mean expect one or more arguments. If we called the function by arithmetic_mean(1, 2, 3, 4), the placeholder $a would be replaced by 1 and the placeholder $b would be replaced by 2, 3, 4 to compute the result. The functions sum and count can handle what is passed to them as they have variadic parameters themselves.
Here is another example that builds upon the previous one. The higher-order function map is used to square all arguments that have been passed to the function before their arithmetic mean is calculated.
root_mean_square(Rat ...Rat) = sqrt(arithmetic_mean(map(x^2 $a $b)));
Must variadic parameters come last?
No, not in Quirl. Any type or wildcard parameter can be variadic, no matter whether it is placed at the beginning, in the middle or at the end of a function's parameter list.
How many variadic parameters can a function have?
There is no particular restriction on the number of variadic parameters. So their maximum number is the same as the maximum number of parameters, twenty-six.
How are arguments assigned to variadic parameters?
Each parameter that is not variadic captures exactly one argument. Variadic parameters capture any further arguments. The order of arguments is preserved of course. So a variadic parameter at the beginning of the parameter list captures the first few arguments while a variadic parameter at the end captures the last few arguments.
The number of arguments captured by multiple variadic parameters is balanced: each variadic parameter gets equally many arguments unless that is not possible, in which case leftmost variadic parameter(s) get one more argument. So the distribution is solely determined by the number of parameters and arguments, not by their type.
Consider the following function with its three wildcard parameters. The first and last parameters are variadic:
foo(...? ? ...?) = [$a] [$b] [$c];
Let's look at three example calls:
-
foo(1)The parameter in the middle expects exactly one argument and one argument was supplied. There are no more arguments to assign to the variadic parameters. But that's okay: variadic parameters can capture zero arguments. The function will return
[], [1], []. -
foo(1 2 3 4 5)We've got five arguments. One argument is expected by the middle parameter, leaving four for the variadic parameters. Since their numbers are balanced, each of the two variadic parameters gets two arguments. The function will return
[1, 2], [3], [4, 5]. -
foo(1 2 3 4 5 6)There are six arguments now. One is expected by the middle parameter, leaving five arguments for the two variadic parameters. Five isn't divisable by two, so the variadic parameters can't have exactly equally many arguments. The left variadic parameter gets one more argument then: three versus two. The function returns
[1, 2, 3], [4], [5, 6].
Calculation
Formally, the balanced distribution of arguments can be derived with a calculation.
- Let
Nbe the number of non-variadic parameters of a function. - Let
Vbe the number of variadic parameters of the function. - Let
Rbe the number of arguments supplied to the function in a call. - Set
M = sub(R, N), that is the number of arguments captured by all variadic parameters together. - Set
L = mod(M, V). - Set
K = intdiv(M, V). - Then the
Lleftmost variadic parameters captureK + 1arguments each while the rest of the variadic parameters captureKarguments each.
What are multiple variadic parameters good for?
Multiple variadic parameters are often useful for divide-and-conquer algorithms. The motivation to implement multiple variadic parameters came from one problem specifically: stack overflows. Let's look at how the count function that returns the number of arguments passed to it was implemented in the past:
count() = 0;
count(? ...?) = add(1 count($b));
It used single recursion: the second mapping of count made a single self-referential call to count with one argument less than it received. Of course this self-referential call called itself again and again as long as there were arguments to count. This made the recursion depth grow linearly with the number of arguments initially supplied to the function. Unfortunately, the strategy fills the rather limited call stack in computers up quickly.
Here is a newer implementation of count with two variadic parameters in the third mapping and double recursion:
count() = 0;
count(?) = 1;
count(? ? ...? ...?) = add(count($a $c) count($b $d));
The third mapping makes two self-referential calls to count, each with half as many arguments than it received. Thanks to the division of the problem size, recursion depth only grows with the binary logarithm of the number of initially supplied arguments. This makes an overflow of the call stack due to the count function practically impossible: you would run into other problems before filling it.
Solutions with a single variadic parameter and single recursion are often, but not always, simpler to comprehend. It is fine to keep it simple. But it is good to know that you can resort to multiple variadic parameters, if you run into practical limits with a single one, isn't it?
How to limit the number of arguments of a function to, say, 100?
You can use a condition in which you count the arguments. For example, the following up_to_hundred function is fine with up to a hundred arguments.
up_to_hundred(...?) : ord(count($a) 100) = "This is fine.";
Don't set a limit just because you can, though.