Open thekid opened 5 months ago
In Kotlin, scope functions solve this:
Person("Tom", age = 12)
|> findFriends(it)
|> storeFriendsList(it)
// Equivalent
Person("tom", age = 12)
.let { findFriends(it) }
.let { storeFriendsList(it) }
Source: https://discuss.kotlinlang.org/t/pipe-forward-operator/2098
If we were to adopt this into PHP, this could be written as:
new Person('Tom', age: 12)
->let(findFriends(...))
->let(storeFriendsList(...))
;
// Yes, also works on non-objects!
$context->userName()->let(stroupper(...));
We could then simply reuse ?->
for null handling!
In Kotlin, scope functions solve this:
The let
function can be implemented relatively easily, even as an optional extension:
diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php
index ac3d93a..adbe64b 100755
--- a/src/main/php/lang/ast/emit/PHP.class.php
+++ b/src/main/php/lang/ast/emit/PHP.class.php
@@ -1076,6 +1076,24 @@ abstract class PHP extends Emitter {
}
protected function emitInvoke($result, $invoke) {
+ if ($invoke->expression instanceof InstanceExpression && 'let' === $invoke->expression->member->expression) {
+ if ('nullsafeinstance' === $invoke->expression->kind) {
+ $t= $result->temp();
+ $result->out->write("null===({$t}=");
+ $this->emitOne($result, $invoke->expression->expression);
+ $result->out->write(')?null:(');
+ $this->emitOne($result, $invoke->arguments[0]);
+ $result->out->write(")({$t})");
+ } else {
+ $result->out->write('(');
+ $this->emitOne($result, $invoke->arguments[0]);
+ $result->out->write(')(');
+ $this->emitOne($result, $invoke->expression->expression);
+ $result->out->write(')');
+ }
+ return;
+ }
+
$this->emitOne($result, $invoke->expression);
$result->out->write('(');
$this->emitArguments($result, $invoke->arguments);
⚠️ However, this would be a BC break for classes with a let
method!
Here's a real-world example from https://github.com/thekid/dialog:
if ($prop= $env->properties($config, optional: true)) {
$this->sources['config']= $prop->readString(...);
}
This could be rewritten as follows:
// Scope function
$env->properties($config, optional: true)?->let(fn($prop) => $this->sources['config']= $prop->readString(...));
// Pipe operator
$env->properties($config, optional: true) ?|> fn($prop) => $this->sources['config']= $prop->readString(...);
// Hacklang pipes with $$ placeholder
$env->properties($config, optional: true) ?|> $this->sources['config']= $$->readString(...);
Proposal
Complement the null-coalescing operator:
Sometimes we want to perform an action if the value is null, and perform an alternative instead:
We already have a "standard" for chaining expressions with the pipe operator. The above without null handling would be:
👉 To accomplish this, this feature request suggests a null-safe pipe operator, which would make the following an equivalent of the above:
Consistency
This is consistent with the null-safe object operator. If we rewrote the above using a
String
class, we could have the following:However, wrapping every primitive string in a
String
instance would introduce quite a bit of runtime and development overhead!See also
?>
?|>
!??
?|>
suggested