Why Does Ruby Use Its Own Syntax for Safe Navigation Operator

Why does Ruby use its own syntax for safe navigation operator?

This answer is based on the discussion of the feature request in Ruby's issue tracking. According to Ruby's author Yukihiro Matsumoto it wouldn't be possible to introduce operator ?. in Ruby because foo? is valid method name and thus it couldn't be parsed. The first candidate for operator was reversed sequence .?. That syntax was already implemented (by Nobuyoshi Nakada) but was later discarded as it was thought to be too close to original syntax introduced by the other languages (that was not feasible as mentioned earlier). The final syntax &. was accepted as suggested by Matsumoto.

Here's the justification for this syntax given by Matsumoto

I think about this for a while, and thinking of introducing &. instead of .?, because:

  • .? is similar to ?. in Swift and other languages, but is different anyway.
  • Since ? is a valid suffix of method names in Ruby, we already see a lot of question marks in our programs.
  • u&.profile reminds us as short form of u && u.profile.

But behavior of &. should be kept, i.e. it should skip nil but recognize false.

This syntax was then released as part of Ruby 2.3.0-preview1.

How does the &.method syntax (safe navigation operator) works in Ruby?

There are 2 seperate operators here:

  1. Safe navigation operator &. - It is safe navigation operator which was introduced in Ruby 2.3.0. It basically returns nil if the callee is nil instead of raising excecption undefined method called for Nil class. eg:

    a = 1
    a.next
    # => 2
    a&.next
    # => 2
    a = nil
    a.next
    # => NoMethodError (undefined method `next' for nil:NilClass)
    a&.next
    # => nil ## No exception, returns nil

    You can read about it more here and documentation

  2. Unary & : This operator is a little more complex. It is almost equivalent to calling #to_proc but not quite that. But for this discussion let us think like that. So, if you have a Proc, calling with & in front of it will call #to_proc on the Proc and convert it into a block

    multiply_by_2 = Proc.new { |x| x * 2 }
    # => #<Proc:0x00007fb4771cf560>
    # &multiply_by_2 almost equivalent to { |x| x * 2 } but is not correct syntax
    [1, 2].map(&multiply_by_2)
    # => [2, 4]
    # equivalent to [1, 2].map { |x| x * 2 }

    But what happens if we give a symbol like :abc to & operator instead of a proc. It will try to call #to_proc on the symbol and ruby has defined Symbol#to_proc which roughly translates to something like this:

    def to_proc
    # this will return some block like { |x| x.send(:abc) }
    lambda { |x| x.send(self) }
    end

    So &:abc roughly translates to this block { |x| x.abc } using the below transformation

    &:abc =====> :abc.to_proc =====> { |x| x.send(:abc) } ====> { |x| x.abc }

    So, instead of doing [1, 2, 3].map { |x| x.next }, you could do [1, 2, 3].map(&:next) as &:next is roughly equivalent to the block { |x| x.next }.

    See unary & (which is the main source of what I have written here) for more reading.

Does Ruby safe navigation operator evaluate its parameters when its receiver is nil?

To quote from the syntax documentation for the safe navigation operator:

&., called “safe navigation operator”, allows to skip method call when receiver is nil. It returns nil and doesn't evaluate method's arguments if the call is skipped.

As such, the arguments of your log method are not evaluated if the logger is nil when you call it as

logger&.log("something happened at #{Time.now}")

With that being said, note that the Ruby core logger offers a different solution to your exact issue, namely to avoid having to evaluate potentially expensive arguments if the log level is to high.

The Ruby core logger implements its add method something like this (simplified):

class Logger
attr_accessor :level

def initialize(level)
@level = level.to_i
end

def add(severity, message = nil)
return unless severity >= level

message ||= yield
log_device.write(message)
end

def info(message = nil, &block)
add(1, message, &block)
end
end

You can then use this as

logger = Logger.new(1)
logger.info { "something happened at #{Time.now}" }

Here, the block is only evaluated if the log level is high enough that the message is actually used.

Does Ruby have syntax for safe navigation operator of nil values, like in Groovy?

In a rails app there is Object#try

So you can do

obj1.try(:obj2).try(:value)

or with a block (as said on comments bellow)

obj.try {|obj| obj.value}

UPDATE

In ruby 2.3 there is operator for this:

obj&.value&.foo

Which is the same as obj && obj.value && obj.value.foo

Ruby - Is it possible to alias a method to the safe navigation operator

I guess you could go with something as simple as

class Object
def fast_try(meth,*args,&block)
self&.public_send(meth,*args,&block)
end
end

For Example:

["string","STRING","pOw"].map do |s| 
s.fast_try(:upcase!)
.fast_try(:chars)
.fast_try(:find, ->{"No S"}) { |a| a == "S" }
.fast_try(:prepend, "!")
end
#=> ["!S",nil,"!No S"]

While your question states, " No, I don't care about the minor behavior differences between try and safe navigation operator.", given the fact that you have written a gem and noted the following

Differences between FastTry and ActiveSupport#try

It is not our goal to maintain any consistency with the ActiveSupport version of the try method. I do however want to maintain a simple list of the differences. Please create a PR or Issue if you find a difference that should be documented here.

Nothing reported yet

I feel it prudent to mention that there are discernible, and potentially poignant, differences between the 2 here is a Repl Spec to show the differences and for the sake of this answer and the fact that the link might die the output of this spec is as follows:

ActiveSupport#try vs. Safe Navigation (&.)
#try
handles would be NoMethodError with nil (using #respond_to?)
does not work on a BasicObject
behaves like a method call
with no method name given
when block_given?
yields self to a block with arity > 0
evaluates block with arity == 0 in the context of self
when no block_given?
raises an ArgumentError
with a method_name given
a non nil object
uses public_send for message transmission
nil
calls NilClass#try and returns nil
#try!
does not handle NoMethodError
does not work on a BasicObject
behaves like a method call
with no method name given
when block_given?
yields self to a block with arity > 0
evaluates block with arity == 0 in the context of self
when no block_given?
raises an ArgumentError
with a method_name given
a non nil object
uses public_send for message transmission
nil
calls NilClass#try and returns nil
&. (safe navigation)
does not handle NoMethodError
raises a SyntaxError with no method name given when block_given?
raises a SyntaxError with no method name given when no block_given?
works on a BasicObject
does not act like a method call
with a method_name given
a non nil object
&. is not called
nil
returns nil without a method call

Using [] with the Safe Navigation Operator in Ruby

Just use the ordinary (non-sugar) form.

request.path.match(/\A\/(?<slug>(?!admin|assets)\w+)/)&.[](:slug)

What is the difference between `try` and `&.` (safe navigation operator) in Ruby

&. works like #try!, not #try.

And here is description of #try! (from documentation):

Same as #try, but will raise a NoMethodError exception if the receiving is not nil and does not implemented the tried method.

So basically it saves you from calling a method on nil, but if an object is presented it will try to call its method as usual.

The quote is from Rails Documentation, and so it's important to emphasize
that Ruby does not provide #try; it's provided by Rails, or more accurately ActiveSupport. The safe navigation operator (&.) however, is a language feature presented in Ruby 2.3.0.

Ruby: Safe-navigation operator, undefined method `call`

This works fine in Ruby 2.3+ :

unreliable&.> 10

For example :

[-5, 0, nil, 5].each do |unreliable|
p unreliable&.> 0
end
# false
# false
# nil
# true

The way you tried it, Ruby expects unreliable to be a callable object such as a Proc :

unreliable = Proc.new{ |*params| puts "unreliable has been called with #{params}" }
unreliable&.(:>, 10)
# unreliable has been called with [:>, 10]
unreliable.call(:>, 10)
# unreliable has been called with [:>, 10]
unreliable&.call(:>, 10)
# unreliable has been called with [:>, 10]
unreliable[:>, 10]
# unreliable has been called with [:>, 10]

With the safe-navigation operator, there's no need to put parens and the method should be a method name, not a symbol (Rails' try expects a symbol).

Safe navigation operator (&.) for nil

foo&.bar is shorthand for foo && foo.bar, so what would you expect the result of the expression nil && nil.nil? to be?

Differences between the ruby 2.3 safe operator &. and CoffeeScript existential operator .?

CoffeeScript's existential operator ? returns true unless a variable
is null or undefined, which makes it analogous to Ruby's nil?

Instead, the new Ruby safe navigation operator &. is used to call a method of an Object that can be nil without raising an exception. If the object is not nil, the method will be executed; otherwise, it returns nil.

Something like this:

obj.try!(:method1).try!(:method2)

if obj && obj.method1
#...
end

becomes:

obj&.method1&.method2

if obj&.method1
#...
end

References:

  • http://coffeescript.org/#lexical-scope
  • https://bugs.ruby-lang.org/issues/11537
  • http://irb.rocks/ruby-safe-operator/


Related Topics



Leave a reply



Submit