
Ruby gaining a safe navigation operator: '.?' - micaeloliveira
https://bugs.ruby-lang.org/issues/11537
======
Udo
I think a good case could be made that, if you're designing a new dynamically
typed language from scratch that doesn't have to support older code, safe
array/object navigation should be the default.

My argument for this is you should design for the most common use case first,
and most commonly people will want '.?' behavior. You can still check
explicitly for null in the other cases, or even introduce a more cumbersome
non-safe navigation operator. The resulting code will overall need fewer
checks, be more readable, you'll have fewer crashes and exceptions, and it
will generally do the right thing.

I occasionally write toy languages and this is one behavior I think I got
right recently. Along the same lines, but as a more extreme measure, I would
argue that invoking a method call on a null object should return null instead
of throwing an exception.

~~~
skrebbel
I wildly disagree, because I get waking nightmares from the amount of implicit
"wtf why doesn't it even enter my method" behavior that would imply, but I
upvoted you because I think it's a very interesting idea and I wonder what
others think.

~~~
Udo
I agree that the invokation idea is relatively wild, that's why I made it into
a separate point. There are somewhat successful languages out there where
sending a message to a null object doesn't do anything. If this is a class of
bug that happens to you frequently, it's probably not for you. But once you
get into the mindset that each method call is prepended by a virtual
conditional, my hunch is you would know pretty quickly why your method isn't
being called.

~~~
bjeanes
And most Objective-C (one of those languages) developers I've talked to about
this would recall nightmares about that behaviour.

~~~
dazmax
The main problem I've seen with Objective-C is due to the fact that nil has
truthiness, so this code is dangerous:

    
    
        if (input.isInvalid()) {
          reject()
        } else {
          accept()
        }
    

If input is accidentally nil, it's an invisible bug.

The other main complaint is when you have a long string of methods
unexpectedly turn out nil at the end, it's a pain to figure out which one it
was.

~~~
uxp
This is entirely incorrect.

nil is a pointer to the value 0, and in C there is only one value that
evaluates to a logical false, which is 0. Invoking a method on nil returns 0.
This itself alleviates the kind of nil-check-chains discussed in this thread:

    
    
        [[[user profile] name] isEqualToString:@"Paul"];
    
        [user profile] && [[user profile] name] && [[[user profile] name] isKindOf:NSString] && [[[user profile] name] isEqualToString:@"Paul"];
    

if user.profile.name is nil, then that expression evaluates to the value 0,
which as we discovered, is the _only value that evaluates to false in C_

------
9point6
I've been hoping this would show up in JavaScript after playing with
CoffeeScript a few years ago. Being able to do:

    
    
        foo?.bar?('baz')
    

was quite nice.

------
nikon
Similar to the new null-conditional operator in C#?

[https://msdn.microsoft.com/en-
us/library/dn986595.aspx](https://msdn.microsoft.com/en-
us/library/dn986595.aspx)

~~~
richmarr
Groovy's predates this one, but yeah, looks similar.

~~~
vorg
I remember Groovy didn't allow ? on indexes like in the third C# example:

    
    
      int? count = customers?[0]?.Orders?.Count();
    

I remember asking for it but it never came. I don't think Ruby's proposed .?
syntax would work with indexes either unless they use [? ].

Neither Java nor Groovy has the int? declaration shown which enforces the
optionality. The C# version looks more versatile.

Back when Groovy was more popular, its backers also discussed a cascading ? on
their mailing list but it never happened. If C# had cascading ? then the
example above would be

    
    
      int? count = customers?[0].Orders.Count();
    

because the first ? would ripple to the subsequent member accesses and index
lookups.

~~~
richmarr
Yep. I remember waving my arms and crying for ES6 to have '?.' back when the
spec was open, but there was a lexical issue.

It's frustrating to have such useful features blocked by such seemingly small
problems as ambiguous patterns of characters.

------
forgotpwtomain
I don't understand why this needs a special syntax -- to me ruby already has a
little too much syntactic variation.

Why not just extend fetch or try e.g. -- .try(:profile, :thumbnails, :large)

~~~
spoiler
I agree with this. I don't see why new syntax (which looks kinda confusing,
IMO) should be added to solve a problem which can be solved with a method. A
naive-2-minute implementation:

    
    
        module Chainable
          def chain(*kl)  
            k = kl.shift
        
            if self.respond_to?(k)
              v = self.public_send(k)
            else
              v = nil
            end
            
            return v if kl.empty?
        
            unless v.respond_to? :chain
              raise ArgumentError, "can't chain on `#{k.inspect} => #{v.inspect}:#{v.class}`" 
            end
            
            return v.chain(*kl)
          end
        end
    

and then this:

    
    
        class Object
          include Chainable
        end

~~~
vidarh
Because the proposed version can be made a lot faster, even before considering
that your version doesn't handle method arguments

~~~
forgotpwtomain
> Because the proposed version can be made a lot faster

It being faster has nothing to do with the syntactical appearance of the
operator. I think it's more pertinent to ask why does .chain_try or .tries or
.fetches need a special operator than whether it's a convenient addition.

~~~
vidarh
It doesn't _need_ a special operator for other reason than that by specifying
the VM-level behaviour it can be made a lot more efficient.

And at that point naming it after something that are legal method names would
be problematic.

------
VeejayRampay
Should be noted that Ruby on Rails has had try [1] for years, which is
something that I've noticed has a tendency to be the classic band-aid for
loose error handling.

[1]
[http://www.rubydoc.info/docs/rails/Object:try](http://www.rubydoc.info/docs/rails/Object:try)

------
maniacalrobot
Seems like a perfectly valid opportunity to use the Interrobang character?

    
    
      foo‽bar("baz")

~~~
madsohm
I don't have that on my keyboard, only ¿.

~~~
renke1
XCompose is your friend. At least when running X.

------
moonshinefe
Doesn't this just encourage lazy and vague programming? It sounds like it cuts
down on a lot of checks in certain situations, but I'm struggling to think of
a coding pattern that's actually well designed that requires so many checks at
the last minute without knowing what type and data you're dealing with.

~~~
moonshinefe
To clarify, in the article linked they give this example:
u.?profile.?thumbnails.?large

Using that operator a single time or whatnot seems handy, but chaining it like
that just seems like poor practice to me.

~~~
deckiedan
It's in deep nesting like that that such an operator really comes into its
own. If you only use it for single cases, it really isn't worth it.

In the article the 'problem' code is:

    
    
        if u && u.profile && u.profile.thumbnails && u.profiles.thumbnails.large
          do_something_with(u.profiles.thumbnails.large)
        fi
    

which repeats u 5 times, and the entire u.profiles.thumbnails.large twice!

which would be replaced by only asking once.

even worse (although somewhat clearer, perhaps) is what I've occasionally
seen:

    
    
        if u
          if u.profile
            if u.profile.thumbnails
               if u.profile.thumbnails.large
                 do_something_with(u.profile.thumbnails.large)
               end
            end
          end
        end
    

And even the super verbose version there still can raise exceptions when you
get slightly mad data - say you decode some JSON which has
u.profile.thumbnails as "null" (a string) rather than actually a null. asking

    
    
        if u.profile.thumbnails
    

will be true - but when you request u.profile.thumbnails.large, you blow up.

You can achieve similar effects using only exceptions, but there's a good
argument for using exceptions only for exceptional circumstances.

    
    
        begin
           do_something_with(u.profile.thumbnails.large)
        rescue NoMethodError #or similar
           # oh no!
        end
    

which (alas) also will catch all noMethodErrors in do_something_with. So you
then end up with

    
    
        begin
          tmp = u.profile.thumbnails.large
        rescue
          tmp = nil
        end
        if tmp
            do_something(tmp)
        end
    

which is super ugly.

I may have got details wrong - not being a rubyist, but I think this is the
general gist of where a nestable operator makes much much cleaner code, which
catches more errors.

~~~
vidarh
And it's worth pointing out that the above is also _slow_ whether you depend
on exceptions or just the long if version, and depends on having methods with
no side-effects, as several methods gets called multiple times.

As far as I see the proposed version will work equivalent to assigning the
result of each method invocation to a temporary, and doing the next check on
that, which gets far more ugly if you have to write it out

------
alexprengere
Interesting timing, _python-ideas_ has also been discussing this, leading to a
new _PEP 505 - None-aware operators_ :

[https://www.python.org/dev/peps/pep-0505/](https://www.python.org/dev/peps/pep-0505/)

------
colinyoung
Maybe we could use something more readable? What about:

    
    
        if posts.comments??.likes??
    

or

    
    
        if posts..comments..likes
    

instead of

    
    
        if posts.?comments.?likes
    

That would mirror the usage of ?? as the nil-coalescing operator in Swift.

------
Slump
Affectionately called the "Elvis operator" by my development team.

~~~
vorg
Elvis is actually the name in Groovy for the ?: syntax (C# uses ??), not for
the ?. syntax.

The name "elvis" was promoted by the then-project manager of Groovy in an
attempt to change the origin meaning of "G-Strings", the name for interpolated
strings in Groovy. It originally alluded to the item of clothing, but in an
attempt to sanitize Groovy's image for marketing, he wanted it to have meant
the string on Elvis's guitar, so he created an accompanying fun-name for the
null coalescing operator.

~~~
Slump
That's fair. I come from a C# background and Mads Torgersen himself refers to
the C#'s ?. syntax as the Elvis operator see
([https://channel9.msdn.com/Events/Build/2015/3-711](https://channel9.msdn.com/Events/Build/2015/3-711))
30:50. It's all in good fun.

------
seivan
Good, I guess it's a way to legitimise .try() I like it.

------
V-2

        Ruby can't use "?." operator.
    
        Can we use ".?" syntax
    

Why couldn't Ruby use "?."?

~~~
pluma
For whatever reason, Ruby allows method names ending with ? or !. AFAIK the
general practice is to use ! for methods that perform a mutation in-place
(e.g. list.sort! would sort a list in place, list.sort would be expected to
return a new list) and to use ? for methods that return a boolean (e.g.
list.empty? would indicate whether the list is empty).

~~~
dragonwriter
> AFAIK the general practice is to use ! for methods that perform a mutation
> in-place

That's not quite the usual statement of the rule: ! is for methods where there
exists a "dangerous" version and a "less dangerous" version, to denote the
more dangerous version. A common example of that is methods where one version
returns a copy of the original object with some modification applied and the
other version modifies the original object in place, but IIRCT that's not the
only distinction it is used for in the standard library and, more importantly,
in-place mutations that aren't paired with a "return-a-modified-copy" method
_don 't_ get the !.

~~~
lsaferite
Please define IIRCT as I cannot find what it's meant to mean.

------
Camillo
That thread has some incredibly bad syntax ideas towards the bottom (#22,
#23). A binary operator with a space in the middle?

------
gotchange
I am not familiar with Ruby except for the casual exposure to it through Sass
but couldn't he just go for only

    
    
      if u.profiles.thumbnails.large
    

instead of these successive checks?

~~~
forgotpwtomain
No, because you cannot access thumbnails on profiles = nil.

~~~
gotchange
Why can't I access it? I'm genuinely curious about this.

~~~
pdkl95
The child objects may not exist. "u" is nil (a null pointer, more or less),
calling "u.profiles" sends the :profiles message to nil. NilClass probably
doesn't have a :profiles method, so this will probably raise a NoMethodError
exception.

As for why this is a common problem, "u.profiles.thumbnails.large" is is
probably a chain of Rails/ActiveRecord model associations[1], which generally
map to table rows . If there the "profiles" table doesn't have any rows "WHERE
profiles.user_id = users.id", then ActiveRecord will leave a nil[2] (or empty
array) in user.profiles. This is true for all foreign key relationships, so
you often end up having to do something to protect against null values (no
rows) every time you move between models.

Sometimes it is possible to work around this by always creating the associated
model when the parent object is created, that only works in a few cases. Note:
I recommend against doing tricks to auto-build the associated model on first
access: that kind of "clever" solution can lead to confusing bugs later on. (I
learned that one the hard way)

[1]
[http://guides.rubyonrails.org/association_basics.html](http://guides.rubyonrails.org/association_basics.html)

[2] ibid, 4.1.1.1 "The association method returns the associated object, if
any. If no associated object is found, it returns nil."

~~~
gotchange
Thanks for the detailed answer. I think it's safe to assume that Ruby is a bit
complicated at least for me but my main point was if he is just interested in
checking for the `large` prop (I am not sure about the proper term here in
Ruby), I'd expect him to just check for it directly and if this fails, he goes
on to the next step as planned and that's why it seemed bit redundant to me
esp. I didn't see any exceptions handling done to make use of the fail of
previous checks on that chain to instruct the end user or dev accordingly.

Again excuse my ignorance of the subject and technical details in Ruby.

