HipHop VM 3.16.0-dev (rel)
Compiler: heads/master-0-g6e071f9bc61705bf78cc03a34bea56187265b3ea
Repo schema: 5f3c657c22fb79db502a22ee010bf3d040eff565
<?hh // strict
interface Runnable<-Tin, +Tout> {
public function run(Tin $data): Tout;
}
// Outputs nothing, but consumes ints
class VoidOut implements Runnable<int, void> {
public function run(int $data): void {}
}
// Outputs ints, but consumes nothing
class VoidIn implements Runnable<void, int> {
public function run(void $data): int {
return 1;
}
}
Code type checks, the $data variable is of type void and cannot be used for anything, and VoidIn::run() must be called with null as input.
Error on the parameter to VoidIn::run():
The void typehint can only be used to describe a function return type (Naming[2063])
I am unsure of what to do in this case. If I have a generic interface with a type parameter used as input, but an implementation that wants to ignore its input, what am I supposed to use as a unit type to fill the type parameter?
I've tried void, (), null and noreturn and none of them work. I could use mixed, but that's a top type, not a unit type, and will permit callers to pass anything as input even though it will be ignored.
Here is a workaround:
final class Nothing {
private function __construct() {
throw new \Exception('This class must not be constructed');
}
}
type void_in = ?Nothing;
The only valid value for void_in is null.
This doesn't actually solve your problem, but an important point for why it works this way:
the
$datavariable is of typevoidand cannot be used for anything, andVoidIn::run()must be called withnullas input.
This is not correct -- null is not of type void. There are no values at all that are of type void, which is the entire point of the type void. This is why a parameter of type void doesn't make sense, since there are no values that could possibly satisfy it.
That was my rough suggestion for how it should work. I know that's not how it works at the moment.
At runtime, function () {}, function () { return; } and function () { return null; } are all identical, so Hack's idea of void and null being distinct only exists artificially at the type level.
php > var_dump((function () {})());
php shell code:1:
NULL
php > var_dump((function () { return; })());
php shell code:1:
NULL
php > var_dump((function () { return null; })());
php shell code:1:
NULL
php >
Ideally, void would serve as a perfectly good unit type without restriction, null would have type void and void would be assignable to any nullable type. I suspect this would also solve #7450 since null would then have a complete type.
At least TypeScript, Flow, Swift, Rust, and most functional programming languages work this way (either using void or ()). For example this is valid TypeScript/Flow.
function foo1(): void {
}
function foo2(): void {
return;
}
function foo3(): void {
return undefined;
}
let x: void = foo3();
function blah(x: void): void {
return x;
}
blah(x);
I think that Hack needs _both_ a bottom type _and_ a unit type. Rust, in particular, has both: any empty enum is a bottom type (even having a value of an empty enum invokes Undefined Behavior, and there is no way to create one in safe code, so any code that involves such a value can be considered unreachable).
@DemiMarie Hack's bottom type is noreturn, but it can only be used as a function's return type. I can't imagine needing to write the bottom type in any other position, but if you do need to do that I think you should open a separate issue.
You can also do:
<?hh
newtype nada = (int, int); // newtypes are opaque outside the file, so as long as there's no way to create one, the following declaration works ...
function nulls_only(?nada $data): void {}
Hack does have the type null now, which I think can function as a unit type? (The important property of a unit type AIUI being that there is exactly one value of that type, which null satisfies.) Kind of weird to read but I think it will work for you?
// Outputs ints, but consumes nothing
class NullIn implements Runnable<null, int> {
public function run(null $data): int {
return 1;
}
}
Edit: yeah, the docs specifically mention this use-case. https://docs.hhvm.com/hack/built-in-types/null
@jwatzman I don't have HHVM anywhere to test it but it sounds good to me!
null is indeed a unit type. In that case, we've got both a unit and bottom type.
Most helpful comment
Hack does have the type
nullnow, which I think can function as a unit type? (The important property of a unit type AIUI being that there is exactly one value of that type, whichnullsatisfies.) Kind of weird to read but I think it will work for you?Edit: yeah, the docs specifically mention this use-case. https://docs.hhvm.com/hack/built-in-types/null