Skip to content

Instantly share code, notes, and snippets.

@morrisonlevi
Last active May 17, 2021 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save morrisonlevi/f7cf949c02f5b9653048e9c52dd3cbfd to your computer and use it in GitHub Desktop.
Save morrisonlevi/f7cf949c02f5b9653048e9c52dd3cbfd to your computer and use it in GitHub Desktop.
This is an attempt to specify how partial function application could work in PHP for positional, named, and variadic cases.

Terminology

  • positional argument: f(expr)
  • named argument: f(x: expr)
  • variadic unpacking: f(...expr)
  • positional placeholder: f(?)
  • named placeholder: f(x: ?)
  • variadic placeholder: f(...)

When any argument of an argument list is a placeholder, then instead of doing a function call it will become a partial function application. A partial function application results in a Closure. The "applied function" is the callable which the resulting Closure will call when it is invoked, passing along the arguments it bound at creation time along with the arguments it receives at invocation time. The created Closure will return the result of the calling the applied function (TODO: new operator)

"To bind" an argument means that the the parameter at that position or name in the applied function is now bound and cannot be used by another binding nor placeholders. Consider this snippet:

function f($x = 0, $y = 0) {}
f(?, $value);

The argument $value is bound to the position of $y in function f, and $y cannot participate in placeholders nor other bindings. For example, this is illegal because $y is bound by both position and by name: f(?, $value, y: $value2)).

Algorithm

  1. From left to right, examine each argument.
    • If it's a positional argument, bind it to the same position in the delegating invocation.
    • If it's a named argument, bind it to the position in the delegating invocation which corresponds to the parameter of the same name in the applied function if it exists, otherwise bind the named argument to the delegating invocation as a trailing named argument.
    • If it's a positional placeholder, create a parameter in the resulting closure and bind the created parameter to the position in the delegating call that corresponds to the placeholder's position (not the created parameter's position). The created parameter has the same name, type, and refness as the corresponding position in the applied function.
    • If it's a named placeholder, create a parameter in the resulting closure and bind it to the position of the parameter in the applied function which has the same name. If the applied function does not have a parameter of that name, bind it as a trailing named argument. The created parameter has the same type and refness as the parameter with the same name in the applied function.
    • If it's the variadic placeholder, create parameters in the resulting closure which correspond to the remaining parameters in the applied function. If the applied function is variadic, also create a varaidic parameter of the same name, type, and refness as the variadic parameter in the applied function.
  2. Error if:
    • The same parameter is ever used twice, such as by name and position or used by name twice.
    • The name of a named placeholder does not exist in the applied function, unless the applied function is variadic.
    • A required parameter of the applied function lacks a placeholder or binding.
    • The variadic placeholder is placed anywhere except in the last argument.
    • Positional placeholders or positional arguments are used after named placeholders or named arguments.

Examples

function rectangle_volume(float $length, float $width, float $height): float {
  return $length * $width * $height;
}

// variables of the same name are equivalent
$A = rectangle_volume(?, ?, ?);
$A = rectangle_volume(...);
$A = fn(float $length, float $width, float $height): float
       => rectangle_volume($length, $width, $height);

$B = rectangle_volume(height: ?, length: ?, width: ?);
$B = fn(float $height, float $length, float $width): float
       => rectangle_volume($length, $width, $height);

// Error because $length is used by name and position
// $C = rectangle_volume(?, length: ?, width: 1, height: 1);

// Error because $width and $height are required parameters and have neither
// placeholders nor bindings.
// $D = rectangle_volume(?); // use rectangle_volume(...) instead

Partials of different kinds of callables

Partial application creates a Closure that calls the applied function and returns the result of calling the applied function. This works for many kinds of "functions":

If you partially apply a function, you get a Closure which will call that function.

If you partially apply an instance/static method, you will get a Closure that calls the instance/static method. TODO: flesh out scope.

If you partially apply an new operator, you will get a Closure that calls the new operator. TODO: anonymous classes.

If you partially apply Closure A, then you get another Closure A-prime that calls the Closure A.

If you partially apply a callable from a string or special array form, then you get a Closure which calls the callable of the string or special array form. TODO: flesh out edges here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment