Skip to main content

Partial functions

You can use a wildcard symbol * to create a reference to a function, that is, to obtain a value of a function type that allows you to call the function:

function f(x: integer, y: integer) = x * y;

val g = f(*); // Type of "g" is (integer, integer) -> integer
g(123, 456); // Invocation of f(123, 456) via "g".

The f() notation is a partial application of a function. It allows specifying values for some parameters and wildcards * for others, returning a value of a function type accepting the parameters for which wildcards were specified:

val g = f(123, *);          // Type of "g" is (integer) -> integer
g(456); // Calls f(123, 456).

val h = f(*, 456); // Type of "h" is (integer) -> integer
h(123); // Calls f(123, 456).

Details of partial application syntax

If a wildcard symbol * is specified for at least one parameter, all the unspecified parameters that don't have default values are also considered wildcards. Thus, the exact value of type (integer, integer) -> integer.

Wildcard symbol * specified as the last parameter without a name has a special meaning. It doesn't correspond to a particular function parameter but determines that the call expression is a partial application. Thus, it's unnecessary to specify * for each function parameter. It's enough to write f(*), regardless of the number of parameters f has (even if it's zero).

note

Suppose a wildcard without a name is specified as the last parameter. In that case, there must be no other wildcard parameters because otherwise, the previous one isn't needed (as the call is already a partial application) and may be confusing.

Parameters with default values that aren't explicitly specified as wildcards are bound to their default values. The default values are calculated at the moment of partial application. Consider the following function:

 function p(x: integer, y: integer = 1) = x * y;

The following expressions return the same value of type (integer) -> integer:

  • p(*)

  • p(x = *)

  • p(x = *, y = 1)

The code to include both parameters into a function type that is to get (integer, integer) -> integer:

 p(y = *)
p(x = *, y = *)

Note, for instance, that rules 1 and 2 imply that for a single-parameter function with a default value:

 function r(x: integer = 123): integer { ... }
  • r(*) returns () -> integer (as the last unnamed * isn't assigned to a particular parameter)

  • r(x = *)) returns (integer) -> integer

The order of named wildcard parameters matters. Consider the following function:

function f(x: integer, y: text, z: boolean): decimal { ... }
  • f(*) is equivalent to f(x = *, y = *, z = *) and returns (integer, text, boolean) -> decimal
  • f(z = *, y = *, x = *) returns (boolean, text, integer) -> decimal
  • f(y = *) is equivalent to f(y = *, x = *, z = *) and returns (text, integer, boolean) -> decimal
  • f(*, z = *) is equivalent to f(x = *, z = *, y = *) and returns (integer, boolean, text) -> decimal

Partial application of system functions

Most system library functions can be partially applied to turn them into function values. However, some library functions are overloaded, for example:

  • abs(integer): integer

  • abs(decimal): decimal

You must know the type of the function value to partially apply an overloaded function:

val f: (integer) -> integer = abs(*);   // The type of variable "f" allows to determine which "abs" to use.

Member functions (methods of types text,list<T>, etc.) can be partially-applied too:

val l = [5, 6, 7];
val f = l.size(*); // Type of "f" is () -> integer.
print(f()); // Prints 3.

l.add(8);
l.add(9);
print(f()); // Prints 5.

Some system functions don't support partial application: print(), log(), require(), text.format(), and so on.