Functions: Calling A Function

A function is called using the function-call operator, (), which optionally contains a comma-separated list of arguments. For example:

$dx = 3.5;
$dy = 2.7;
$hypot = \sqrt($dx*$dx + $dy*$dy);

Here, we use Pythagoras' Theorem to find the hypotenuse of a right-triangle. The call to the library function sqrt contains one argument, of type float, and it returns a value of the same type.

Hack supports recursion; that is, a function can call itself directly or indirectly. For example:

<?hh // strict

namespace Hack\UserDocumentation\Functions\Calling\Examples\Recursion;

function factorial(int $i): int {
  return ($i > 1) ? $i * factorial($i - 1) : $i;
}

<<__EntryPoint>>
function main(): void {
  for ($i = 0; $i <= 10; ++$i) {
    \printf("%2d! = %d\n", $i, factorial($i));
  }
}
Output
0! = 0
 1! = 1
 2! = 2
 3! = 6
 4! = 24
 5! = 120
 6! = 720
 7! = 5040
 8! = 40320
 9! = 362880
10! = 3628800

By default, arguments are passed by value; however, if a parameter contains the inout modifier, the corresponding argument must also contain that modifier. See the swap function in $$ for an example.

Function calls in the presence of FormatString

The typechecker is "aware" of certain functions whose arguments are inter-related, so it can check they are called correctly. One such function is \printf (and its sibling, \sprint). Consider the following:

\printf("Hello, World!\n");
\printf("Value is %d\n", 6 + 4);
\printf("Decimal: %d, HEX: %X, Unsigned: %u\nBinary: %b\n", -1, -1, -1, -1);

from which this output is produced:

Hello, World!
Value is 10
Decimal: -1, HEX: FFFFFFFFFFFFFFFF, Unsigned: 18446744073709551615
Binary: 1111111111111111111111111111111111111111111111111111111111111111

The contents of the first argument string are used to determined whether more arguments follow, and, if so, how they are to be handled. The expectation of further arguments and their handling are determined by the presence of conversion specifiers, which begin with %. In the first case, there are none, so the text Hello, World!\n is simply written out to standard output. In the second case, %d is seen, so another argument is expected, and it is output as a signed decimal integer. In the third case, %d, %X, %u, and %b are seen, so another four arguments are expected, with their values being output as a signed decimal integer, uppercase hexadecimal integer, unsigned decimal integer, and binary integer, respectively.

The typechecker also makes sure that the number of arguments actually provided matches the number expected. For example:

\printf("Value is %d\n");                      // Error: too few arguments
\printf("Values are %d and %d\n", 10, 20, 30); // Error: too many arguments

It is important to note that the first argument must be a string literal:

$format = "Value is %d\n";
\printf($format, 6 + 4); // Error: format string must be a string literal

In the examples above, the type of the argument passed exactly matches that expected; however, what if that was not the case? Consider the following:

\printf("Value is %d\n", 6.0 + 4.0);     // Value is 10
\printf("Value is %d\n", 6.901 + 4.123); // Value is 11

The expression 6.0 + 4.0 has type float, yet %d says to format the result as a signed integer. Yes, but the runtime can then convert float to int first. And as there are no fractional parts, no precision is lost, and 10 is output. In the case having fractional parts, the two values are added resulting in the float 11.024, which is then truncated to the int 11.

Here are some more examples:

\printf("Value is %d\n", false);        // Value is 0
\printf("Value is %d\n", vec[]);        // Value is 0
\printf("Value is %f\n", true);         // Value is 1.000000
\printf("Value is %f\n", vec[10,20]);   // Value is 1.000000

According to Hack's conversion rules, when converted to int (or float), false is 0 (or 0.0) and true is 1 (or 1.0). And an empty vec is converted to 0 (or 0.0), while a non-empty one is converted to 1 (or 1.0).

But what if the argument cannot be converted to the expected form? That is flagged as an error:

\printf("Value is %d\n", new C()); // Error: class C could not be converted to int

In the earlier example in which a string variable rather than a literal was given as the first argument, the actual error message produced by the typechecker is something like the following:

This argument must be a literal string
The function expects an object of type HH\FormatString, which is incompatible with string

Function \printf is declared, as follows:

function printf(\HH\FormatString<PlainSprintf> $fmt, ...$fmt_args): int;

FormatString is a generic library type, and while we don't need to understand it to use \printf, clearly, the string literals—which have type string---we've been passing as the first argument are permitted when this type is expected, but a variable of type string is rejected.

Examples of other functions that take an argument of this type are invariant and queryf.