Hello Internals,
Today, I was working with a library (ReVolt) that throws if a callback
returns something other than null (why it doesn't ignore the value, I
don't know, maybe I'll file an issue). I discovered the topic of this
email after trying to be simple:
EventLoop::repeat($pingInterval, $client->ping(...));
Where ping()
will return a bool which triggers an error since it
returns something. I thought to myself, cool, I'll just cast the
result to null and everything will be fine:
EventLoop::repeat($pingInterval, fn() => (null) $client->ping());
However, this isn't allowed and fails with a parse error.
I won't be the first to say this, at first glance, casting to null
sounds silly, but short arrow functions must always return something,
by design. That's when casting to null makes any sense at all (that I
can think of): you want to write a succinct, short function but
guarantee the result is discarded. Instead, if you really must use a
short array function, you have to do something even weirder:
EventLoop::repeat($pingInterval, fn() => $client->ping() ? null : null);
I assume casting to null was discussed previously (at some point,
though I didn't see anything), but what are your thoughts? Is this
something that even makes sense? I'll admit, I've never tried casting
to null before, but I had assumed it would "just work" and was
slightly surprised that it didn't.
Robert Landers
Software Engineer
Utrecht NL
Hi Robert
I won't be the first to say this, at first glance, casting to null
sounds silly, but short arrow functions must always return something,
by design. That's when casting to null makes any sense at all (that I
can think of): you want to write a succinct, short function but
guarantee the result is discarded. Instead, if you really must use a
short array function, you have to do something even weirder:EventLoop::repeat($pingInterval, fn() => $client->ping() ? null : null);
While (void) $client->ping() would solve your problem, it's not very
useful outside this scenario.
I think there are two issues you're implicitly referring to.
- Arrow functions cannot be void, because they always return something.
- Arrow functions cannot contain multiple statements.
As for the former, PHP actually had a similar issue for never closures
that was solved a while ago [1]. In the report I suggested that the
same could be done for void, by making void arrow functions evaluate
and drop the right hand side of =>, and always return nothing (i.e.
null). This would solve your issue, although probably mostly by
accident (because void functions return null, and your caller expects
exactly null). Regardless, I think this change would be useful, if
just to signal that the return value of an arrow function is not
intended to be used.
The latter would require some sort of block or grouped expression.
Short closures have been discussed extensively in the past, so I won't
get into that. There's also the comma operator in some languages like
C and JavaScript that evaluates a list of expressions and returns the
result of the last one, although probably not universally liked (i.e.
fn () => ($client->ping(), null)).
[1] https://github.com/php/php-src/commit/f957e3e7f17eac6e9195557f3fa79934f823fd38
Ilija
I don't even know why lambda realization is not the same as in javascript.
let action = function () {
console.log('hello');
return 1;
}
let fn1 = () => action();
console.log(fn1()); // calls hello(), returns 1, for me, not a best way,
could be read like "call action() then create function that returns result
of that call"
let fn2 = () => (action());
console.log(fn2()); // calls hello(), returns 1, common, short way
// what's this?
(() => ())(); // could look strange? no, its declaring unnamed function
that does nothing and returns nothing and calls it immediately.
let fn3 = () => { action(); }
console.log(fn3()); // calls hello(), returns undefined, common, short
without return
let fn4 = () => action;
console.log(fn4()); // returns function, very handy for Promises, that
start init function immediate on new call, so it wraps function to "task"
available to call it later somewhere, maybe in another promise
Why is the PHP implementation redesigned from scratch instead of using the
idea of the one that works?
ps. Guess, the discussion to change syntax that you cold rewrite with
single line:
// > what?
// fn() => $client->ping() ? null : null
// > that
// static function () { $client->ping(); }
Ahh, the main problem in fn() is exactly that you don't like.... use
statement)))) So long to write the use
keyword.
Аnd you prefer the js
implementation, which automatically uses the
current scope in your callback.
And you need something handy, that is allow create functions:
- with all variables (copy local state)
- with short syntax
- with return modes variety
- with return types and parameter types, if you need it
is more interesting, than talk about error collection that requires
rewriting half of the app
As I remember you say, you work on projects with a million lines of code
and you prefer correctness instead of simplification.
Why do you use short functions then? OOP awaits you.
Create a function that wraps function:
- one function returns result (client.ping())
- second one returns null|result (myclass.clientPingOrNull())
- The third one catches the first one with a try catch to return the
result and return errors (myclass.tryClientPingOrNull()) - then call that batch of correctness in your callback...
Sorry, couldn't resist myself
EventLoop::repeat($pingInterval,
function(...$args)use($client){$client->ping(...$args)});