Mailing List Archive

Pre-RFC: Optional Chaining
Hello, Porters!

I am super excited to bring you this pre-RFC feature request, and humbly
ask you to review and let me know whether it has what it takes to go
further up the RFC road.

I have tried my best to properly fill all fields from the RFC template even
though I understand this is just the very first stage. Still, I think it
would be a great addition to the language - I'd certainly use it everywhere.

Thank you!
garu

-----------------------8<-----------------------
# Optional Chaining

## Preamble

Author: Breno G. de Oliveira <garu@cpan.org>
Sponsor:
ID:
Status: Draft

## Abstract

This RFC proposes a new operator, `?->`, to indicate optional dereference
chains.

## Motivation

Chained dereferencing of nested data structures is quite common in Perl
programs. However, and specially due to Perl's dynamic type system, there
are
times when you must check whether the data exists, is defined and holds a
reference before you can dereference it, otherwise the call may trigger a
runtime exception.

The current syntax for these verifications can be quite long, becoming even
longer the more nested verifications need to be done, making it not only
harder to write and read, but also more prone to human error.

## Rationale

An "optional chaining" operator would let us access values located deep
within
a chain of conected references and fail gracefully without having to check
that each of them is valid. This should result in shorter, simpler and more
correct expressions whenever the value being accessed may be missing, or
when
exploring objects and data structures without guarantees of which
keys/methods/indexes/accessors are provided.

Equivalent solutions have been thoroughly validated by many other popular
programming languages, which already provide this feature going back since
at least 2015. It is sometimes also referred to as a "safe call", "null
safe",
"safe navigation" or "optional path" operator.

JavaScript, Kotlin, C#, Swift, TypeScript, Groovy, all provide "?.", which
behaves, to the best of my knowledge, exactly as proposed here. Ruby also
does it, but uses "&.". PHP does it with "?->" like what we're proposing.

Raku implements it as ".?" instead of "?." to denote that, in its case,
it is checking whether the invocant has the righthand side method, not
whether
the invocant itself is defined.

Rust provides the "?" operator to collapse Result objects into
either their Ok value or their Err value
(e.g. File::open("hello.txt")?.read_to_string(&mut s)?)
This behaves somewhat similarly to what is being proposed here.

Python, Java and C++, notably, do not provide it yet - though PEP505 has
been
proposed for the former.

Another noteworthy mention is Objective-C, in which every chained call
automatically short-circuits to NULL without requiring a special operator.

## Specification

The `?->` operator would behave exactly like the current dereference arrow
`->`, except that, instead of causing a runtime error if the lefthand side
is undefined or not a reference, it would shortcut the whole expression
to `undef` in scalar or void context, and an empty list `()` in list
context.

As with `->`, spaces would be allowed before and after the `?`, so
`$obj?->method` can be written as `$obj ?-> method` or even `$obj ? ->
method`.

The `?->` operator would interact with the exact same things that the `->`
operator does and should be completely interchangeable with it, albeit
providing the short-circuit.

## Backwards Compatibility

This would not conflict, to the best of my knowledge, with any other syntax
in Perl 5. All code with `?->` currently yields a compile time syntax error,
except inside interpolated strings (where `"$foo?->{bar}"` resolves to
showing the reference address followed by a literal `?->{bar}`) and regular
expressions (where `?` acts as a special character and `/$foo?/` gives no
warnings, though I am not entirely sure of its purpose). These would need to
be updated to handle optional chains, even though it should be pointed that
trying to use `undef` inside a string or a regexp already yields an
`Use of uninitialized value` runtime warning, and would continue to do so
with the optional chain operator.

Outside the interpreter, since this would be a new operator, I am not sure
how hard it would be for static tools and modules to incorporate it.
However,
considering it would of course go through the whole 'use experimental' and
'use feature' process, I expect it to be about as hard as it was for the
postfix dereference implementation on those same tools, and maybe that can
be
used as a ballpark estimate.

Finally, because it involves a special token (`?`), I don't think it is
possible to emulate it for earlier Perl versions via a module, unless it's a
source filter.

## Security Implications

None foreseen.

## Examples

Here are a few use case examples, with their current Perl 5 equivalent
in the comments:

no warnings 'experimental::optional_deref'; # or something.
use feature 'optional_deref';


$x = $obj?->method; # $x = ref $obj ? $obj->method : undef;
# or $x = $obj->method if ref $obj;

$x = $obj?->method?->other; # my $tmp = ref $obj ? $obj->method :
undef;
# then $x = $tmp->other if $tmp;
# Note that in this case we need a
temporary
# storage to avoid calling ->method twice.

$x = $href?->{somekey}; # $x = ref $href ? $href->{somekey} :
undef;

$x = $href?->{foo}?->{bar}; # $x = ref $href && ref $href->{foo}
# ? $href->{foo}{bar} : undef;

# NOTE: I'd rather write the statement below as
$href?->{foo}?{bar}?{baz}
# but it may be harder for the compiler (see "Open Issues" below):
if ($href?->{foo}?->{bar}?->{baz} == 42) # if ( ref $href
# && ref $href->{foo}
# && ref
$href->{foo}{bar}
# &&
$href->{foo}{bar}{baz}
# == 42)

$x = $subref?->(42); # $x = ref $subref ? $subref->(42) : undef;

$x = $a?->[3]?->[0]; # $x = ref $aref && ref $aref->[3] ?
$aref->[3][0]
# : undef;

# A notable exception that does not use 'ref':
$x = SomeNamespace?->new; # $x = %SomeNamespace:: ? SomeNamespace->new
# : undef;

# attribution would, of course, also be allowed:
$href?->{foo}?->[3] = 'OHAI!'; # $href->{foo}[3] = 'OHAI'
# if ref $href && ref $href->{foo};

# postfix dereferencing:
%x = $href?->%*; # ref $href ? $href->%* : undef;
$x = $aref?->$#*; # ref $aref ? $aref->$#* : undef;

my $aref; say foreach $aref?->@*; # no loop, no warnings.
# behaves the same as foreach
$aref->@*

@x = $aref?->@*; # @x is now (), not (undef). Equivalent to:
# @x = ref $aref ? $aref->@* : ()

$x =~ s/\d+/$href?->{foo}/e; # $x =~ s/\d+/ref $href ? $href->{foo}
# : undef/e;


## Prototype Implementation

I think it may be possible to implement this with a source filter, but
I have not attempted to do so.

## Future Scope

Future versions may be cleverer and do extra checks on the statement
considering both sides of the operator, thus making it even more useful.

For example, `$href?->{a}?->{b}?->[3]?->{d}` is, according to this proposal,
equivalent to:

ref $href
&& ref $href->{a}
&& ref $href->{a}{b}
&& ref $href->{a}{b}[3]
? $href->{a}{b}[3]{d} : undef;

But it could be made equivalent to something like:

ref $href eq 'HASH'
&& exists $href->{a}
&& ref $href->{a} eq 'HASH'
&& exists $href->{a}{b}
&& ref $href->{a}{b} eq 'ARRAY'
&& length(@{$href->{a}{b}}) >= 4
&& ref $href->{a}{b}[3] eq 'HASH'
&& exists $href->{a}{b}[3]{d}
? $href->{a}{b}[3]{d} : undef;

Similarly, `$x = $obj?->a?->b` could be made equivalent to something like:

my $tmp = ref $obj && blessed($obj) && $obj->can('a') ? $obj->a : undef;
$x = ref $tmp && blessed($tmp) && $tmp->can('b') ? $tmp->b : undef;

None of these potential future updates would interfere with the proposed
syntax, just with how far Perl would be willing and able to safeguard it.

Another potentially interesting area of scope would be to allow for defining
a custom default value other than `undef`. This could be achieved by
offering similar syntax to the ternary operator, but it goes beyond the
scope of this RFC.

## Rejected Ideas

I am unaware whether this feature has already been proposed for Perl 5
in the past.

We could, potentially, achieve the same result by making `undef` respond to
any calls as `undef`, much like what Objective-C does, so `$x->{a}[3]{b}`
becomes `undef->{a}[3]{b}` then `undef->[3]{b}` then `undef->{b}` then
`undef`. The problem with this approach is that it completely eliminates the
runtime exception of trying to use undef as a reference, which may not be
what the developer wants. It would, in fact, potentially create problems for
existing programs that count on that runtime error to happen. Instead, I
believe it's better to make it explicit.

I have considered going the path of Raku and proposing the operator as `->?`
instead of `?->` but having the `?` closer to the invocant feels more like
what developers may expect, not only for its similarities with
implementations
in other languages but also because it reads like a ternary `?:`, which may
make it easier to read and glance over. In other words, I believe `$x?->y`
reads more like the intended behavior, whereas `$x->?y`, to me, reads like
Perl dereferenced the invocant before the check. Finally, postderefs would
be
even harder to read, e.g.: `$aref->?$#*`.

I have also considered using other token(s) for this, but having the `->` to
signal the dereference is something all Perl 5 developers have come to
expect,
and the `?` is the obvious choice not just for its similarity with other
implementations, but because of its behavioral similarities with the ternary
operator.

## Open Issues

* Would we be able to unambiguously omit the arrow in chained references?

I think, at least for an initial implementation, the arrow needs to be
mandatory for optional chaining even in cases where the lone arrow can be
omitted (notably, chained hash/array references), since allowing `?`
followed by a lot of other tokens could, potentially, become ambiguous with
ternary ops. That said, full compatibility would be great, and I have not
investigated enough to prove said ambiguity. In other words, it would be
great
to be able to write `$x?->{foo}?{bar}?{baz}` instead of
`$x?->{foo}?->{bar}?->{baz}`. Maybe in a future version?

* Other identity values?

I believe short-circuiting to `undef` in scalar context and `()` in list
context is a good start. Still, there may be other interesting "identity"
values to be returned depending on the variable being dereferenced or maybe
even its context and surroundings, like for example short-circuiting to the
empty string when being interpolated. Those may not be so obvious (what is
the
identity value for a subref, or a globref?) and therefore it would need to
be
done in a case by case manner, very thoroughly considering each one in order
to keep the syntax consistent and predictable, not magical.

## References

*
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-
* https://kotlinlang.org/docs/null-safety.html#safe-calls
* http://www.groovy-lang.org/operators.html#_safe_navigation_operator
* https://wiki.php.net/rfc/nullsafe_operator
* https://www.python.org/dev/peps/pep-0505/
* https://docs.raku.org/language/operators#methodop_.?
* https://bugs.ruby-lang.org/issues/11537
* https://tc39.es/proposal-optional-chaining/
*
https://doc.rust-lang.org/std/result/index.html#the-question-mark-operator-
*
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html
* https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html

## Copyright

Copyright (C) 2021, Breno G. de Oliveira

This document and code and documentation within it may be used,
redistributed
and/or modified under the same terms as Perl itself.

----------------------->8-----------------------
Re: Pre-RFC: Optional Chaining [ In reply to ]
From tokenizer and grammar point of view it will be easier to use ->? syntax
you don't need to modify current toke.c, you will only need to alter
grammar, by adding "optquestionmark".
Re: Pre-RFC: Optional Chaining [ In reply to ]
I proposed something similar when I first joined this mailing list, which
was a new keyword "it" which would be an alias for the last thing found to
exist by the "exists" predicate, which IIRC I also somehow modified to not
autoviv intermediates.

As autovivifying intermediate data structure layers is a core part of Perl,
it seems like continuing to use the tried and true workarounds is the way
to go rather than trying to scratch this itch.

Sorry.

On the other hand, we do have support for foo($bar{baz}) not creating a
nonexistent $bar{baz} unless $_[0] gets assigned to within foo, so there's
that: it should be possible to fairly easily craft a new module that
exports a function, say "optichain", that you could call like so

my $find_without_autoviv = optichain( \%bar, baz=>blarf=> );

and that would set $find_without_autoviv to undef when $bar{baz} does not
already exist, without autovivving it.

it might look something like this:

sub optichain{
my $container = shift;
@_ or return $container;
ref $container or return $container;
my $index = shift;
if("$container" =~ /HASH/ ){
unshift @_ $container{$index} }
elsif("$container" =~ /ARRAY/){
unshift @_ $container[$index] }
else{ carp "please do something about support of things
like >>>$container<<< as optichain containers" };
goto &optichain; # look, professor! Tail recursion!
}


--
"Lay off that whiskey, and let that cocaine be!" -- Johnny Cash
Re: Pre-RFC: Optional Chaining [ In reply to ]
2021-10-29 2:39 breno <oainikusama@gmail.com> wrote:

> Hello, Porters!
>
> I am super excited to bring you this pre-RFC feature request, and humbly
> ask you to review and let me know whether it has what it takes to go
> further up the RFC road.
>
> I have tried my best to properly fill all fields from the RFC template
> even though I understand this is just the very first stage. Still, I think
> it would be a great addition to the language - I'd certainly use it
> everywhere.
>
> Thank you!
> garu
>
> -----------------------8<-----------------------
> # Optional Chaining
>
> ## Preamble
>
> Author: Breno G. de Oliveira <garu@cpan.org>
> Sponsor:
> ID:
> Status: Draft
>
> ## Abstract
>
> This RFC proposes a new operator, `?->`, to indicate optional dereference
> chains.
>
>
breno

Thank you for your posting.

Is it possible to start with a short conversation in Pre-RFC?

This is for reducing the cost of reading.

And it can be painful if a lot of writing is denied.
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, 28 Oct 2021 14:39:15 -0300
breno <oainikusama@gmail.com> wrote:

> Hello, Porters!
>
> I am super excited to bring you this pre-RFC feature request, and humbly
> ask you to review and let me know whether it has what it takes to go
> further up the RFC road.
>
> I have tried my best to properly fill all fields from the RFC template even
> though I understand this is just the very first stage. Still, I think it
> would be a great addition to the language - I'd certainly use it everywhere.
>
> Thank you!
> garu

As described in our RFC repo's README[1], pre-RFCs are supposed to be
short, 4-paragraph pitches, *not* complete RFC drafts.

Leaving that aside, I generally support this proposal. However, I'm not
sure if it's a good idea to make the operator work with *defined*
non-references. I think it's something that is rarely needed in code
that follows good practices and it would often hide real bugs.

[1] - https://github.com/Perl/RFCs/blob/master/README.md
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Fri, 29 Oct 2021 02:49:55 +0200
Tomasz Konojacki <me@xenu.pl> wrote:

> Leaving that aside, I generally support this proposal. However, I'm not
> sure if it's a good idea to make the operator work with *defined*
> non-references. I think it's something that is rarely needed in code
> that follows good practices and it would often hide real bugs.

To clarify: the same goes for references of the wrong type. I think it
should only suppress the errors caused by undefined values.
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, Oct 28, 2021 at 9:13 PM Yuki Kimoto <kimoto.yuki@gmail.com> wrote:

> Is it possible to start with a short conversation in Pre-RFC?
>
> This is for reducing the cost of reading.
>

Sure! Sorry for jumping the gun, my idea was to make things better, not
worse.

The tl;dr version is this: when you have little/no control over a
variable's content, a lot of repetitive and noisy boilerplate code is
required to fail gracefully (without runtime errors) while checking deep
chains of nested code like $data->{some}{deeply}{nested}[0]{content}
because, with strict and warnings, if any part of that chain is defined but
not a ref, you'll get a runtime exception. So if you want to check if that
value equals to 5 you do something like this:

if (ref $data eq 'HASH'
&& ref $data->{some} eq 'HASH'
&& ref $data->{some}{deeply} eq 'HASH'
&& ref $data->{some}{deeply}{nested} eq 'ARRAY'
&& ref $data->{some}{deeply}{nested}[0] eq 'HASH'
&& $data->{some}{deeply}{nested}[0]{content} == 5
) {
do_something()
}

My proposal is for a new "optional chain" operator, namely "?->", that
would fail gracefully whenever the lefthand side of the operator is not a
reference by returning undef in scalar context or an empty list ()
otherwise. So we could rewrite the code above as:

if ($data?->{some}?->{deeply}?->{nested}?->[0]?->{content} == 5) {
do_something();
}

Of course, you could write a scoped "no strict 'refs'" and keep the
original comparison, or wrap the code under eval/try, but maybe you *want*
part of that chain to yield a runtime exception. It gets even worse with
objects, because "no strict 'refs'" will not help you and fetching
something like SomeModule->new->this->then->that where any of those is
undef or does not provide the desired accessor will trigger a "Can't locate
object method X" runtime error. To check if that value equals to 5, for
example, you may need to do something like:

use Scalar::Util qw(blessed);
my $tmp = SomeModule->new;
if (blessed($tmp) && $tmp->can('this')) {
my $tmp2 = $tmp->this;
if (blessed($tmp2) && $tmp2->can('then')) {
my $tmp3 = $tmp2->then;
if (blessed($tmp3) && $tmp3->can('that') && $tmp3->that == 5) {
do_something()
}
}
}

Note that you need the temporary variables unless you're sure calling those
accessors more than once will not cause any side-effects.

With the proposed optional chaining operator, we could write the code above
as:

if (SomeModule?->new?->this?->then?->that == 5) {
do_something();
}

Again, you could wrap all that in an eval/try block, but it may be the case
where you only want to make the validation for certain parts of the chain,
and either way you'll have to check the error message to see if it was just
because the (sub)object wasn't there or because it crashed inside one of
the calls.

It's important to note this is not an original idea: many popular languages
like Rust, JavaScript, Kotlin, C#, PHP and Ruby already implement some form
of this since at least 2015.

Hope this is a more palatable version. Thank you again for taking the time
to review this and consider it for inclusion into the language.

Cheers!
garu
Re: Pre-RFC: Optional Chaining [ In reply to ]
On 2021-10-28 10:39 a.m., breno wrote:
> This RFC proposes a new operator, `?->`, to indicate optional dereference
> chains.

Thank you for addressing the bike-shedding question why ?-> rather than ->? in
your rejected ideas section.

From a language idiom consistency and semantics point of view, my biggest
potential issue is the distinction between definedness and truthiness, and what
the question mark conceptually better fits with, as fitting with both might not
be ideal.

-- Darren Duncan
Re: Pre-RFC: Optional Chaining [ In reply to ]
Op 28-10-2021 om 19:39 schreef breno:
> This RFC proposes a new operator, `?->`, to indicate optional dereference
> chains.


Yes, please!


>     # NOTE: I'd rather write the statement below as
> $href?->{foo}?{bar}?{baz}
>     # but it may be harder for the compiler (see "Open Issues" below):
>     if ($href?->{foo}?->{bar}?->{baz} == 42)   # if (   ref $href
>                                                # && ref $href->{foo}
>                                                # && ref $href->{foo}{bar}
>                                                # && $href->{foo}{bar}{baz}
>                                                #     == 42)


## Open Issues
>
> * Would we be able to unambiguously omit the arrow in chained references?
>
> I think, at least for an initial implementation, the arrow needs to be
> mandatory for optional chaining even in cases where the lone arrow can be
> omitted (notably, chained hash/array references), since allowing `?`
> followed by a lot of other tokens could, potentially, become ambiguous
> with
> ternary ops. That said, full compatibility would be great, and I have not
> investigated enough to prove said ambiguity. In other words, it would
> be great
> to be able to write `$x?->{foo}?{bar}?{baz}` instead of
> `$x?->{foo}?->{bar}?->{baz}`. Maybe in a future version?


Agree. I would probably not miss it. I find it ugly, where ?-> is elegant.


>
> * Other identity values?
>
> I believe short-circuiting to `undef` in scalar context and `()` in list
> context is a good start. Still, there may be other interesting "identity"
> values to be returned depending on the variable being dereferenced or
> maybe
> even its context and surroundings, like for example short-circuiting
> to the
> empty string when being interpolated. Those may not be so obvious
> (what is the
> identity value for a subref, or a globref?) and therefore it would
> need to be
> done in a case by case manner, very thoroughly considering each one in
> order
> to keep the syntax consistent and predictable, not magical.


Maybe $href?->{foo} : "default value"; ? Becomes unreadable, but
ternaries are unreadable anyway and I cannot think of a better alternative.


M4
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, 28 Oct 2021 14:39:15 -0300
breno <oainikusama@gmail.com> wrote:

> Hello, Porters!
>
> I am super excited to bring you this pre-RFC feature request, and
> humbly ask you to review and let me know whether it has what it takes
> to go further up the RFC road.

Overall: a "pre-RFC" is supposed to be a paragraph or two of "should I
write this RFC?" as a simple yes/no question. What you've written here
is already the full RFC. :)

> Finally, because it involves a special token (`?`), I don't think it
> is possible to emulate it for earlier Perl versions via a module,
> unless it's a source filter.

XS::Parse::Infix could parse it, on a suitable perl build with the
PL_infix_plugin support. That would let you experiment with this as a
CPAN module first.

https://metacpan.org/pod/XS::Parse::Infix#DESCRIPTION

> ## Rejected Ideas
>
> I am unaware whether this feature has already been proposed for Perl 5
> in the past.
>
> We could, potentially, achieve the same result by making `undef`
> respond to any calls as `undef`, much like what Objective-C does, so
> `$x->{a}[3]{b}` becomes `undef->{a}[3]{b}` then `undef->[3]{b}` then
> `undef->{b}` then `undef`. The problem with this approach is that it
> completely eliminates the runtime exception of trying to use undef as
> a reference, which may not be what the developer wants. It would, in
> fact, potentially create problems for existing programs that count on
> that runtime error to happen. Instead, I believe it's better to make
> it explicit.

Definitely agree.

> I have considered going the path of Raku and proposing the operator
> as `->?` instead of `?->` but having the `?` closer to the invocant
> feels more like what developers may expect, not only for its
> similarities with implementations
> in other languages but also because it reads like a ternary `?:`,
> which may make it easier to read and glance over. In other words, I
> believe `$x?->y` reads more like the intended behavior, whereas
> `$x->?y`, to me, reads like Perl dereferenced the invocant before the
> check. Finally, postderefs would be
> even harder to read, e.g.: `$aref->?$#*`.

Likewise - given the positioning of the "?" there I would expect

$obj?->method # equivalent to $obj->method if defined $obj;

$obj->?method # equivalent to $obj->method if $obj->can("method")


--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Fri, 29 Oct 2021 at 18:35, Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
wrote:

> On Thu, 28 Oct 2021 14:39:15 -0300
> breno <oainikusama@gmail.com> wrote:
> > I have considered going the path of Raku and proposing the operator
> > as `->?` instead of `?->` but having the `?` closer to the invocant
> > feels more like what developers may expect, not only for its
> > similarities with implementations
> > in other languages but also because it reads like a ternary `?:`,
> > which may make it easier to read and glance over. In other words, I
> > believe `$x?->y` reads more like the intended behavior, whereas
> > `$x->?y`, to me, reads like Perl dereferenced the invocant before the
> > check. Finally, postderefs would be
> > even harder to read, e.g.: `$aref->?$#*`.
>
> Likewise - given the positioning of the "?" there I would expect
>
> $obj?->method # equivalent to $obj->method if defined $obj;
>
> $obj->?method # equivalent to $obj->method if $obj->can("method")
>

Raising this suggestion for completeness (I like the suggested `?->` as-is):

`//->` would be more consistent with our existing `//` defined-or operator,
since `? :` already sets the precedent that the `?` is checking for true,
rather than defined.

There is perhaps an argument for the `?->` operation to be _entirely_ based
on `ref`, allowing:

%x = (x => "example");
is($x{example}?->{this_is_ignored}, 'example');

The RFC suggests that instead this would return `undef`, which I think may
be a bit confusing?

but +1 for the overall concept from me - existing workarounds are
universally terrible.
Re: Pre-RFC: Optional Chaining [ In reply to ]
On 2021-10-29 4:44 a.m., Tom Molesworth via perl5-porters wrote:
> On Fri, 29 Oct 2021 at 18:35, Paul "LeoNerd" Evans wrote:
>
> Raising this suggestion for completeness (I like the suggested `?->` as-is):
>
> `//->` would be more consistent with our existing `//` defined-or operator,
> since `? :` already sets the precedent that the `?` is checking for true, rather
> than defined.

I was totally thinking this too though didn't say it in my other reply, that
using //-> would be more consistent with the existing defined-or operator, while
?-> speaks to booleans. -- Darren Duncan
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, Oct 28, 2021, at 1:39 PM, breno wrote:
> Hello, Porters!

Hello! I will try to be brief.

> ## Specification
>
> The `?->` operator would behave exactly like the current dereference arrow
> `->`, except that, instead of causing a runtime error if the lefthand side
> is undefined or not a reference, it would shortcut the whole expression
> to `undef` in scalar or void context, and an empty list `()` in list context.

I think a few others have said: I think this needs to be based on defined, not ref. To throw another example, on the pile:

package Class {
use Moose;
has friend_factory => (is => 'ro', default => 'Friend' );
}

my $pal = Class->new->friend_factory?->new;

I want to be able to call methods on class names safely, too.

I am a little uncertain about the spelling, but don't have a better option *and* my concern is ridiculous: I like that Ruby allows predicate methods, ending in "?", and this would get in the way of that… if we made it legal. So maybe ignore that.

> As with `->`, spaces would be allowed before and after the `?`, so
> `$obj?->method` can be written as `$obj ?-> method` or even `$obj ? -> method`.

This feels somewhat perverse. Why is ?-> not a single and inseparable token?

The other concern I have is with string interpolation. "postfix_qq" is still a bit of a pain and feels like a mistake. Options include, at least:
* just declare `"$x?->{foo}"` acts like `"" . $x?->{foo}` and we don't worry about the backcompat, as we assume it's wildly unlikely
* the same, but we always keep this behind a feature flag so everyone has opted in
* we find a syntax that is not presently legal instead
* we do not allow this in interpolation, allowing it to work someday via something like JavaScript template literals, to be invented at a future date
The second seems tolerable. I would love to have the thing imagined in the 4th, but not keen to make ?-> contingent on that.

--
rjbs
Re: Pre-RFC: Optional Chaining [ In reply to ]
On 28-Oct-21 19:39, breno wrote:
>
> * Would we be able to unambiguously omit the arrow in chained references?
>
> I think, at least for an initial implementation, the arrow needs to be
> mandatory for optional chaining even in cases where the lone arrow can be
> omitted (notably, chained hash/array references), since allowing `?`
> followed by a lot of other tokens could, potentially, become ambiguous
> with
> ternary ops. That said, full compatibility would be great, and I have not
> investigated enough to prove said ambiguity. In other words, it would
> be great
> to be able to write `$x?->{foo}?{bar}?{baz}` instead of
> `$x?->{foo}?->{bar}?->{baz}`. Maybe in a future version?
>
How about this?

Let
    $foo?->{a}{b}{c}
mean
    $foo?->{a}?->{b}?->{c}
while
    $foo->{a}{b}{c}
continues to mean
    $foo->{a}->{b}->{c}

In other words, an omitted arrow in a chained reference is optional or
not depending on the previous explicit arrow, -> or ?-> .

If you need to, you can still write
    $foo?->{a}{b}->{c}{d}
if you need
    $foo?->{a}?->{b}->{c}->{d}
or
    $foo->{a}{b}?->{c}->{d}
if you want
    $foo->{a}->{b}?->{c}->{d}

This should be backwards compatible, and seems dwimmy enough to me.

 – Michael
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Sun, Oct 31, 2021, at 9:31 AM, Michael Schaap wrote:
> In other words, an omitted arrow in a chained reference is optional or
> not depending on the previous explicit arrow, -> or ?-> .

I think this would be a mistake for at least three reasons:

1. It would not match the behavior of other popular versions of this operator, like JavaScript's. Why surprise people?

2. It would mean that these two pieces of code would be different, which I, at least, think is confusing and complicates refactoring:
# FIRST
$x?->{a}->{b}->{c};

# SECOND
$y = $x?->{a};
$y->{b}->{c};

3. It means that although we're adding a new operator that lets you pick between "safe" or "unsafe" arrow within an expression, we can only choose "safe" *once* before the choice is taken away for the rest of the expression. It's less powerful.

--
rjbs
Re: Re: Pre-RFC: Optional Chaining [ In reply to ]
On 31-Oct-21 15:10, Ricardo Signes wrote:
> On Sun, Oct 31, 2021, at 9:31 AM, Michael Schaap wrote:
>> In other words, an omitted arrow in a chained reference is optional or
>> not depending on the previous explicit arrow, -> or ?-> .
>
> I think this would be a mistake for at least three reasons:
>
> 1. It would not match the behavior of other popular versions of this
> operator, like JavaScript's.  Why surprise people?
Are you sure?
JavaScript , as far as I know, doesn't /have/ a way to omit the . or ?.
in chained references.
(I don't know if other languages do, and if so, how they handle it.)

>
> 2. It would mean that these two pieces of code would be different,
> which I, at least, think is confusing and complicates refactoring:
> # FIRST
> $x?->{a}->{b}->{c};
>
> # SECOND
> $y = $x?->{a};
> $y->{b}->{c};
>
No, these would be the same.  If you do include an arrow, they would
indicate optional (?->) or non-optional(->) for the next argument(s).
In this case, though, the code would have a different meaning:

# FIRST
$x?->{a}{b}{c};

# SECOND
$y = $x?->{a};
$y{b}{c};


and you do have a point that this may be confusing and complicates
refactoring.


> 3. It means that although we're adding a new operator that lets you
> pick between "safe" or "unsafe" arrow within an expression, we can
> only choose "safe" /once/ before the choice is taken away for the rest
> of the expression. It's less powerful.
>
No, you can always use $x?->{a}{b}->{c}{d}?->{e}{f} to switch between
optional and not optional.  (I'm not sure which one you consider
"safe".  :-) )

 – Michael
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Sun, Oct 31, 2021, at 10:31 AM, Michael Schaap wrote:
> On 31-Oct-21 15:10, Ricardo Signes wrote:
>> On Sun, Oct 31, 2021, at 9:31 AM, Michael Schaap wrote:
>>> In other words, an omitted arrow in a chained reference is optional or
>>> not depending on the previous explicit arrow, -> or ?-> .
>>
>> I think this would be a mistake for at least three reasons:
>>
>> 1. It would not match the behavior of other popular versions of this operator, like JavaScript's. Why surprise people?
> Are you sure?
> JavaScript , as far as I know, doesn't *have* a way to omit the . or ?. in chained references.
> (I don't know if other languages do, and if so, how they handle it.)

I see. I had misunderstood that you were talking about *omitted* arrows. I think making the behavior of the implicit arrow different is *also* a bad idea, largely for the "refactoring becomes more complex" argument I provided.

--
rjbs
Re: Pre-RFC: Optional Chaining [ In reply to ]
Language consistency issue

why only operator -> should be affected? Why excluding other operators?
(eg: =~)

it would be nice to write eg:
```
my ($bar, $baz) = $foo =~? m/.../
```

instead of
```
my ($bar, $baz);
($bar, $baz) = $foo =~ m/.../ if defined $foo;

Extrapolated to any operator: there should be mechanism (similar to SQL
NULL handling) to say "if undefined here, result of operation is undef", eg?
```
$foo +? $bar
$foo ?+ $bar
$foo ?+? $bar
$foo++?

Question: are optional expressions evaluated?
Example:
```
$counter = 0;
$foo->?[$counter++]->?{$counter++}->?bar ($counter++);
```
What will be value of $counter?

Brano
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Sun, Oct 31, 2021, at 4:59 PM, Branislav Zahradník wrote:
> Language consistency issue
>
> why only operator -> should be affected? Why excluding other operators? (eg: =~)

Because this part of the problem is tractable and familiar.

We have a generic mechanism for doing operation X to $foo iff $foo is defined:

defined $foo ? X($foo) : ()

?-> is sugar for one very specific case, which can be considered pretty closely.

For something more generic, what's the underlying principle? "All binary operators can be prefixed with a question mark to short circuit."? (You wrote "any" operator in part of your message.)

Surely not the ".." operator, though, right? Can you use the "?//" operator? What about "?=="?

The other thing you cited is SQL NULL. In SQL, the language has a deeply-baked ternary logic system that Perl just doesn't. It feels like a big lift.

The big difference here is that one often uses -> to drill down through a series of references. "?+" isn't particularly useful. In arithmetic code, you might write "$x + $y / $z % $w". If any of these is undef and you believe this should void the function, you'd use a ternary or other boolean logic to just check their definedness. You're not likely to say "$x ?+ $y / $z ?% $w" unless you're being exceptionally clever, and *also*, addition is a commutative operation, so is ?+ checking both sides?

I think ?-> makes sense. ?=~ is plausible, but I think already much less likely to be useful. ?=~ can be chained with s///r, but usually is not, and in other cases, using a ternary is probably simpler.

As for everything else, I think those roads leads to madness.

--
rjbs
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Sun, Oct 31, 2021 at 06:04:24PM -0400, Ricardo Signes wrote:
> On Sun, Oct 31, 2021, at 4:59 PM, Branislav Zahradn?k wrote:
> > Language consistency issue
> >
> > why only operator -> should be affected? Why excluding other operators? (eg: =~)
>
> The other thing you cited is SQL NULL. In SQL, the language has a deeply-baked ternary logic system that Perl just doesn't. It feels like a big lift.
>
> The big difference here is that one often uses -> to drill down through a series of references. "?+" isn't particularly useful. In arithmetic code, you might write "$x + $y / $z % $w". If any of these is undef and you believe this should void the function, you'd use a ternary or other boolean logic to just check their definedness. You're not likely to say "$x ?+ $y / $z ?% $w" unless you're being exceptionally clever, and *also*, addition is a commutative operation, so is ?+ checking both sides?

You could also do:

no warnings "uninitialized";

if you don't care about whether this follows SQL NULL rules, but this can't work for ?->

Tony
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, Oct 28, 2021 at 08:21:53PM +0200, Branislav Zahradn?k wrote:
> >From tokenizer and grammar point of view it will be easier to use ->? syntax
> you don't need to modify current toke.c, you will only need to alter
> grammar, by adding "optquestionmark".

The change to toke.c would be close to trivial.

I'm not sure you could avoid allowing '->?' to be spelled as '-> ?' if you did
this in the parser, I haven't looked closely.

Tony
Re: Pre-RFC: Optional Chaining [ In reply to ]
The other thing you cited is SQL NULL. In SQL, the language has a
> deeply-baked ternary logic system that Perl just doesn't. It feels like a
> big lift.
>
>
That was my point, this pre-RFC defines it (ternary logic system) but only
in small part.


> The big difference here is that one often uses -> to drill down through a
> series of references.
>

"drill down" sounds to me like a data query language


>
>
> As for everything else, I think those roads leads to madness.
>
>
Inconsistency and unclear/unexpected behaviour leads to madness.
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Sun, 31 Oct 2021 at 23:36, Tony Cook <tony@develop-help.com> wrote:

> On Thu, Oct 28, 2021 at 08:21:53PM +0200, Branislav Zahradník wrote:
> > >From tokenizer and grammar point of view it will be easier to use ->?
> syntax
> > you don't need to modify current toke.c, you will only need to alter
> > grammar, by adding "optquestionmark".
>
> The change to toke.c would be close to trivial.
>
> I'm not sure you could avoid allowing '->?' to be spelled as '-> ?' if
> you did
> this in the parser, I haven't looked closely.
>

you can but you need to have two to tokens:
question_mark: QUESTION_MARK | QUESTION_MARK_AFTER_WHITE




>
> Tony
>
Re: Pre-RFC: Optional Chaining [ In reply to ]
Sorry for coming late to the game ...

On Fri, Oct 29, 2021 at 07:44:22PM +0800, Tom Molesworth via perl5-porters wrote:
> On Fri, 29 Oct 2021 at 18:35, Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
> > Likewise - given the positioning of the "?" there I would expect
> > $obj?->method # equivalent to $obj->method if defined $obj;
> > $obj->?method # equivalent to $obj->method if $obj->can("method")
>
> Raising this suggestion for completeness (I like the suggested `?->` as-is):
>
> `//->` would be more consistent with our existing `//` defined-or operator,
> since `? :` already sets the precedent that the `?` is checking for true,
> rather than defined.

See also my proposal a while back for a defined-match operator, in
<20190221115541.GB11111@bytemark.barnyard.co.uk>.

--
David Cantrell | Reality Engineer, Ministry of Information

More people are driven insane through religious hysteria than
by drinking alcohol. -- W C Fields
Re: Pre-RFC: Optional Chaining [ In reply to ]
On Thu, Oct 28, 2021, at 13:39, breno wrote:
> I am super excited to bring you this pre-RFC feature request, and humbly ask you to review and let me know whether it has what it takes to go further up the RFC road.

This morning in the PSC's weekly call, we were discussing this proposal and its status. I said I'd write something back to the list.

Having re-read the posts, I think there was a lot of work in the weeds, and the pre-RFC question sounds like "yes of course people would like something like this," and then we get to the actual RFC question. But Breno's pre-RFC was basically an RFC and could be filed as such, then to be worked on until ready for someone to have a crack at implementing.

My comments on the PSC call this morning were mostly that I felt the most valuable simplification would be to provide an explanation of ?-> in terms of what it's equivalent to. For example:

EXPR1 ?-> EXPR2

# is equivalent to

defined EXPR1 ? EXPR1->EXPR2 : undef

# with the caveat that EXPR1 is only evaluated once

I think the objection (made by a few, including me) that we need to use "defined" (as I do in the snippet above) rather than "ref" as the test should be taken into account in any re-submission of the RFC.

But is there anything left to do but…
1. submit the RFC in an updated form
2. discuss whether it's ready to implement in that form
3. eventually say it's ready for implementing
?

--
rjbs

1 2  View All