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 ofu && u.profile
.But behavior of
&.
should be kept, i.e. it should skipnil
but recognizefalse
.
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:
Safe navigation operator
&.
- It is safe navigation operator which was introduced in Ruby 2.3.0. It basically returnsnil
if the callee isnil
instead of raising excecptionundefined 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 nilYou can read about it more here and documentation
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 blockmultiply_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 definedSymbol#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) }
endSo
&: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 isnil
. It returnsnil
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
Initialize the Delayed Jobs Gem by Starting the Workers on Application Start
Ruby Selenium Web Driver: How to Count Child Element Nodes of a Specific Node
Accessing a Ruby Hash with a Variable as the Key
Dangerousattributeerror in Omniauth Railscast Tutorial: Create Is Defined by Activerecord
New Way of Creating Hashes in Ruby 2.2.0
Nameerror: Undefined - Have Parsing Rules for Local Variables Changed in Ruby 2.1.2
How to Test Order-Conscious Equality of Hashes
Ruby Sequel: Array Returned by Query Is Being Returned as a String Object, Not an Array Object
Static Variables in Ruby, Like in C Functions
Share Session Between Two Rails4 Applications
Has_Many and No Method Error Issue
How to Get the File Creation Time in Ruby on Windows
Active Resource Complaining About Expects an Hash
How to Convert This Ruby String into an Array