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

I am trying to compare a number literal with the return value of a function that could return nil or a numeric. Consider this:
def unreliable
[nil, 42].sample
end
unreliable > 10
This will blow up 50% of the time with NoMethodError: undefined method '>' for nil:NilClass. So I tried this:
unreliable&.(:>, 10)
That approach does the nil guarding I expect, but I get this when unreliable returns 42:
NoMethodError: undefined method `call' for 42:Fixnum
I suspect this has to do with the quirks of allowing only one instance to exist for each Numeric, see here. And I know I can do this:
foo = unreliable
foo && foo > 10
But is there a way to use the safe navigation operator with a numeric and :>, :<, :==, :+, :-, :/, :*, etc?
Edit: The focus on Numeric in my question is a red herring. See #Jörg's answer. I was confusing Rails's try syntax with the safe-navigation operator's syntax.

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).

I suspect this has to do with the quirks of allowing only one instance to exist for each Numeric
No, this has nothing to do with that at all.
foo.(bar)
is syntactic sugar for
foo.call(bar)
Ergo,
foo&.(bar)
is syntactic sugar for
foo&.call(bar)
So, your code:
unreliable&.(:>, 10)
is syntactic sugar for
unreliable&.call(:>, 10)
I'm not sure who told you that the safe-navigation operator takes the message as a symbol argument. The whole point of the safe-navigation operator is that you only have trivial syntactic overhead by adding a single character, the & in front of the ., and the expression is otherwise unchanged.
So,
unreliable > 10
which is the same as
unreliable.>(10)
simply becomes
unreliable&.>(10)

Related

What does shorthand operator like (+=) in ruby really does?

While I can understand that x += 1 is equivalent to x = x + 1, I'm interested what it does in the background.
I've tried in irb with x += 1 without first assigning value to x and surely I get an error, it says
NoMethodError (undefined method `+' for nil:NilClass)
When I tried checking x value after it errored, it's now a nil value which isn't quite clear when it was assigned, unless it's done by x += 1 statement.
So, I'm trying to understand how the += does thing as I could not find the documentation for it or it being buried behind the lot of ruby classes. Any pointer would be appreciated.
Ruby treats identifiers as a variables if it sees assignment (=) even if the assignment fails.
irb(main):001:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> a = 1 if false
=> nil
irb(main):003:0> a
=> nil
irb(main):004:0>
Even though a=1 never executes, a gets defaulted to nil.
Analysis
Defining our Terms
First of all, + and += are methods, not keywords or operators. More accurately, + is defined as Integer#+, while += is syntactic sugar provided by the interpreter as shorthand for a common operation such as incremented assignment.
Secondly, += and related operations are called abbreviated assignments in Ruby. The full list of supported assignment shorthand items currently include:
["+=",
"-=",
"*=",
"/=",
"%=",
"**=",
"&=",
"|=",
"^=",
"<<=",
">>=",
"||=",
"&&="]
The only non-method operators in Ruby are:
["=", "..", "...", "!", "not", "&&", "and", "||", "or", "!=", "!~"]
and shorthand assignment operators like += are not user-definable.
Defining the Problem
The behavior you're seeing has to do with scope, and how the parser handles block-local variables. Specifically, the Ruby assignment documentation says:
[A] local variable is created when the parser encounters the assignment, not when the assignment occurs[.]
That means that top-level code such as:
x
#=> NameError (undefined local variable or method `x' for main:Object)
raises a NameError exception because defined? x #=> nil. However, the parser auto-instantiates local variables when there's an assignment, so that the following works (sort of), at least in the sense that it doesn't raise NameError:
# x is autovivified as nil
x = x + 1
#=> NoMethodError (undefined method `+' for nil:NilClass)
However, nil doesn't have a NilClass#+ method, so you get NoMethodError instead of NameError. This is because:
nil.respond_to? :+
#=> false
Solutions
The basic solution is to explicitly ensure your variable exists within the current scope, and that it is not nil. You can do this in a variety of ways, but the simplest thing is just to assign it directly. For example:
# assign zero to x unless it's truthy
x ||= 0
# perform incrementing assignment
x += 1
Alternatively, you can drop the shorthand and cast the autovivified x as an Integer, which does have a :+ method. For example, assuming x is currently undefined (e.g. you've restarted irb, or you've set x to nil):
x = x.to_i + 1
#=> 1

Why can't I use curly braces with a `for` loop in Ruby?

The Ruby Documentation says that "do/end is equivalent to curly braces", so why is it that when I attempt to do the following I do not receive any output:
a = [1, 2, 3]
for i in a {
puts i
}
When I perform the above I receive no output (but I don't receive an error message either). However, when I do the following everything is as it should be:
a = [1, 2, 3]
for i in a do
puts i
end
#=> 1
#=> 2
#=> 3
I know this can be done more idiomatically with the each statement, but that's not what I'm asking. What am I not understanding here?
The Ruby Documentation says that "do/end is equivalent to curly braces"
No, it doesn't. It says (bold emphasis mine):
In this context, do/end is equivalent to curly braces […]
What "in this context" means is defined directly before the half-sentence you quoted:
do
Paired with end, can delimit a code block
So, "in this context" here refers to the context of a block.
so why is it that when I attempt to do the following I do not receive any output
Because this is a completely different context, again quoting from the documentation you linked to:
do can also (optionally) appear at the end of a for/in statement. (See for for an example.)
The "also" in that sentence makes it very clear that this is a different usage of the keyword do that has nothing to do with the usage discussed in this section. And if you look at the documentation of for, you can see that there is no mention of curly braces being allowed.
When I perform the above I receive no output (but I don't receive an error message either).
That is not true. Your code is syntactically invalid because it is missing the end keyword to end the for/in expression, therefore you get a "syntax error, unexpected end-of-input" on line 4:
ruby -c -e 'a = [1, 2, 3]
for i in a {
puts i
}'
# -e:4: syntax error, unexpected end-of-input
And if you add the missing end, you get a in `<main>': undefined method `a' for main:Object (NoMethodError) on line 2:
ruby -e 'a = [1, 2, 3]
for i in a {
puts i
}
end'
# -e:2:in `<main>': undefined method `a' for main:Object (NoMethodError)
Again, this is expected because curly braces delimit a code block, so
a {
puts i
}
is interpreted as a code block being passed to a and since variables cannot receive arguments, only methods can, a must be a method. Therefore, Ruby rightfully complains about not finding a method named a.
There are three ways of delimiting the iterator expression from the loop body expression in a for/in loop (and the same applies to while and until loops, actually):
An expression separator. An expression separator can either be
a semicolon ;
a newline
The keyword do
So, the following would all be valid fixes for your code:
# non-idiomatic
for i in a; puts i end
# non-idiomatic
for i in a
puts i end
# the same but with idiomatic indentation and whitespace
for i in a
puts i
end
# idiomatic
for i in a do puts i end
# redundant, non-idiomatic
for i in a do
puts i
end
Note, that when I say "idiomatic" above, that is to be interpreted relative, since actually for/in loops as a whole are completely non-idiomatic, and you would rather do this:
a.each do |i|
puts i
end
or maybe
a.each(&method(:puts))
It is in general preferred to not mix I/O and data transformation, so another idiomatic solution would be to transform the data to the desired output first, then output it, like this:
puts a.join("\n")
Except that Kernel#puts will already treat Array arguments special and print each element on its own line (as documented at IO#puts), so the real correct idiomatic solution for your code would be just:
puts a
Take a look to the documentation here: For loop
It states:
Like while and until, the do is optional. The for loop is similar to
using each, but does not create a new variable scope.
And also
The for loop is rarely used in modern ruby programs.
So, be less Pythonic :) using Enumerator#each instead:
a.each { |a| puts a }

Why do you have to specify 2 arguments explicitly to curry :>

Consider this, which works fine:
:>.to_proc.curry(2)[9][8] #=> true, because 9 > 8
However, even though > is a binary operator, the above won't work without the arity specified:
:>.to_proc.curry[9][8] #=> ArgumentError: wrong number of arguments (0 for 1)
Why aren't the two equivalent?
Note: I specifically want to create the intermediate curried function with one arg supplied, and then call then call that with the 2nd arg.
curry has to know the arity of the proc passed in, right?
:<.to_proc.arity # => -1
Negative values from arity are confusing, but basically mean 'variable number of arguments' one way or another.
Compare to:
less_than = lambda {|a, b| a < b}
less_than.arity # => 2
When you create a lambda saying it takes two arguments, it knows it takes two arguments, and will work fine with that style of calling #curry.
less_than.curry[9][8] # => false, no problem!
But when you use the symbol #to_proc trick, it's just got a symbol to go on, it has no idea how many arguments it takes. While I don't think < is actually an ordinary method in ruby, I think you're right it neccessarily takes two args, the Symbol#to_proc thing is a general purpose method that works on any method name, it has no idea how many args the method should take, so defines the proc with variable arguments.
I don't read C well enough to follow the MRI implementation, but I assume Symbol#to_proc defines a proc with variable arguments. The more typical use of Symbol#to_proc, of course, is for a no-argument methods. You can for instance do this with it if you want:
hello_proc = :hello.to_proc
class SomeClass
def hello(name = nil)
puts "Hello, #{name}!"
end
end
obj = SomeClass.new
obj.hello #=> "Hello, !"
obj.hello("jrochkind") #=> "Hello, jrochkind!"
obj.hello("jrochkind", "another")
# => ArgumentError: wrong number of arguments calling `hello` (2 for 1)
hello_proc.call(obj) # => "Hello, !"
hello_proc.call(obj, "jrochkind") # => "Hello, jrochkind!"
hello_proc.call(obj, "jrochkind", "another")
# => ArgumentError: wrong number of arguments calling `hello` (2 for 1)
hello_proc.call("Some string")
# => NoMethodError: undefined method `hello' for "Some string":String
Note I did hello_proc = :hello.to_proc before I even defined SomeClass. The Symbol#to_proc mechanism creates a variable arity proc, that knows nothing about how or where or on what class it will be called, it creates a proc that can be called on any class at all, and can be used with any number of arguments.
If it were defined in ruby instead of C, it would look something like this:
class Symbol
def to_proc
method_name = self
proc {|receiver, *other_args| receiver.send(method_name, *other_args) }
end
end
I think it is because Symbol#to_proc creates a proc with one argument. When turned into a proc, :> does not look like:
->x, y{...}
but it looks like:
->x{...}
with the requirement of the original single argument of > somehow tucked inside the proc body (notice that > is not a method that takes two arguments, it is a method called on one receiver with one argument). In fact,
:>.to_proc.arity # => -1
->x, y{}.arity # => 2
which means that applying curry to it without argument would only have a trivial effect; it takes a proc with one parameter, and returns itself. By explicitly specifying 2, it does something non-trivial. For comparison, consider join:
:join.to_proc.arity # => -1
:join.to_proc.call(["x", "y"]) # => "xy"
:join.to_proc.curry.call(["x", "y"]) # => "xy"
Notice that providing a single argument after Currying :join already evaluates the whole method.
#jrochkind's answer does a great job of explaining why :>.to_proc.curry doesn't have the behavior you want. I wanted to mention, though, that there's a solution to this part of your question:
I specifically want to create the intermediate curried function with one arg supplied, and then call then call that with the 2nd arg.
The solution is Object#method. Instead of this:
nine_is_greater_than = :>.to_proc.curry[9]
nine_is_greater_than[8]
#=> ArgumentError: wrong number of arguments (0 for 1)
...do this:
nine_is_greater_than = 9.method(:>)
nine_is_greater_than[8]
# => true
Object#method returns a Method object, which acts just like a Proc: it responds to call, [], and even (as of Ruby 2.2) curry. However, if you need a real proc (or want to use curry with Ruby < 2.2) you can also call to_proc on it (or use &, the to_proc operator):
[ 1, 4, 8, 10, 20, 30 ].map(&nine_is_greater_than)
# => [ true, true, true, false, false, false ]

Why is ** optional when "splatting" keyword arguments?

Given this method definition:
def foo(a = nil, b: nil)
p a: a, b: b
end
When I invoke the method with a single hash argument, the hash is always implicitly converted to keyword arguments, regardless of **:
hash = {b: 1}
foo(hash) #=> {:a=>nil, :b=>1}
foo(**hash) #=> {:a=>nil, :b=>1}
I can pass another (empty) hash as a workaround:
foo(hash, {}) #=> {:a=>{:b=>1}, :b=>nil}
But, this looks pretty cumbersome and awkward.
I would have expected Ruby to handle this more like arrays are handled, i.e.:
foo(hash) #=> {:a=>{:b=>1}, :b=>nil}
foo(**hash) #=> {:a=>nil, :b=>1}
And using literals:
foo({b: 1}) #=> {:a=>{:b=>1}, :b=>nil}
foo(b: 1) #=> {:a=>nil, :b=>1}
foo(**{b: 1}) #=> {:a=>nil, :b=>1}
The current implementation looks like a flaw and the way I was expecting it to work seems obvious.
Is this an overlooked edge case? I don't think so. There's probably a good reason that it wasn't implemented this way.
Can someone enlighten me, please?
As for the lack of ** part:
My guess is that, to make method invocation simple, Ruby always once interprets the key: value form without the braces as a hash with omitted braces, whether it is actually going to be interpreted as such hash or as keyword arguments.
Then, in order to interpret that as keyword arguments, ** is implicitly applied to it.
Therefore, if you had passed an explicit hash, it will not make difference to the process above, and there is room for it to be interpreted either as an actual hash or as keyword arguments.
What happens when you do pass ** explicitly like:
method(**{key: value})
is that the hash is decomposed:
method(key: value)
then is interpreted as a hash with omitted braces:
method({key: value})
then is interpreted either as a hash or as a keyword argument.
As for keyword arguments having priority over other arguments, see this post on Ruby core: https://bugs.ruby-lang.org/issues/11967.

Couldn't understand the difference between `puts{}.class` and `puts({}.class)`

As the anonymous block and hash block looks like approximately same. I was doing kind of playing with it. And doing do I reached to some serious observations as below:
{}.class
#=> Hash
Okay,It's cool. empty block is considered as Hash.
print{}.class
#=> NilClass
puts {}.class
#=> NilClass
Now why the above code showing the same as NilClass,but the below code shows the Hash again ?
puts ({}.class)
#Hash
#=> nil
print({}.class)
#Hash=> nil
Could anyone help me here to understand that what's going one above?
I completely disagree with the point of #Lindydancer
How would you explain the below lines:
print {}.class
#NilClass
print [].class
#Array=> nil
print (1..2).class
#Range=> nil
Why not the same with the below print [].class and print (1..2).class?
EDIT
When ambiguity happens with local variable and method call, Ruby throws an error about the fact as below :
name
#NameError: undefined local variable or method `name' for main:Object
# from (irb):1
# from C:/Ruby193/bin/irb:12:in `<main>'
Now not the same happens with {} (as there is also an ambiguity between empty code block or Hash block). As IRB also here not sure if it's a empty block or Hash. Then why the error didn't throw up when IRB encountered print {}.class or {}.class?
The precedence rules of ruby makes print{}.class interpreted as (print{}).class. As print apparently returns a nil the class method returns #NilClass.
EDIT: As been discussed on other answers and in the updates to the question, print{} it of course interpreted as calling print with a block, not a hash. However, this is still about precedence as {} binds stronger than [] and (1..2) (and stronger than do ... end for that matter).
{} in this case is recognized as block passed to print, while [] unambiguously means empty array.
print {}.class # => NilClass
print do;end.class # => NilClass
You are running into some nuances of Ruby, where characters mean different things depending on context. How the source code is interpreted follows rules, one of which is that {} is a closure block if it follows a method call, and otherwise a Hash constructor.
It's common throughout the language to see characters mean different things depending on context or position within the statement.
Examples:
Parens () used for method call or for precedence
print(1..5).class => NilClass
print (1..5).class => Range <returns nil>
Square brackets [] used to call :[] method or for Array
print[].class => NoMethodError: undefined method `[]' for nil:NilClass
print([].class) => Array <returns nil>
Asterisk * used for multiplication or splatting
1 * 5 => 5
[*1..5] => [1, 2, 3, 4, 5]
Ampersand & used for symbol -> proc or logical and
0 & 1 => 0
[1, 2, 3].map(&:to_s) => ["1", "2", "3"]
Or in your case, braces used for block closures or for a hash
... hope it makes sense now ...

Resources