Ruby unary operator `&` only valid on method arguments - ruby

as you might know, Ruby has a shorthand operator & which let's us easily convert a Symbol to a Proc. Example:
%w(a b c).map(&:upcase) #=> ["A", "B", "C"]
Would be equivalent to:
%w(a b c).map { |c| c.upcase } #=> ["A", "B", "C"]
And the explanation is that &:upcase is calling to_proc on :upcase to create a Proc object.
So, that pretty much explains what the operator does when it's used as the last argument of a method.
However, looks like it's not possible to use the operator anywhere outside the params of a method call:
:upcase.to_proc => #<Proc:0x007febbc85ab38(&:upcase)>
&:upcase => SyntaxError: syntax error, unexpected &
This would have been nice to use like this:
case number
when &:even?
# ...
when &:prime?
# ...
end
This works though:
case number
when :even?.to_proc
# ...
when :prime?.to_proc
# ...
end
In short, the unary operator & can only be used in the arguments of a method, for example in arr.map(&:upcase). What is the reason for that?

The & unary prefix ampersand operator "unpacks" a Proc (or an object that can be converted to a Proc by sending it the to_proc message) into a block, passing it as if it had been passed as a block. Only message sends can have block arguments, ergo the & unary prefix ampersand operator is only allowed for the last argument in an argument list to a message send.
Likewise, the "dual" unary prefix ampersand operator "packs" a block into a Proc and is thus only allowed for the last parameter in a parameter list of either a block or a method definition.

Related

Passing symbol to block methods

Some block methods such as inject can optionally take a symbol instead of a block:
%w[a b c].inject(&:+)
# => "abc"
%w[a b c].inject(:+)
# => "abc"
%w[a b c].inject("", :+)
# => "abc"
while other block methods such as map cannot:
%w[a b c].map(&:upcase)
# => ["A", "B", "C"]
%w[a b c].map(:upcase)
# => ArgumentError: wrong number of arguments (1 for 0)
Why can't the latter take a symbol?
For inject, a block (or a substitute) is obligatory. If it weren't passed a block, then there has to be at least one argument, the last argument has to be a symbol, and the block would be constructed out of it. Whatever the arity, there is no ambiguity; the last argument is used to construct a block when the block is lacking.
For map, a block is optional. When there is no block given, then the return value would be an Enumerator instance. Hence, from the information whether a block was passed or not, it cannot be decided whether the last argument should be used to construct a block.
In the particular case of map, it does not take an argument, so there is a sense in saying that an extra argument should be taken as a block, but it makes things complicated to judge whether that last argument is to be taken as a block depending on the arity. And it also loses the future possibility of changing the arity of the method.
Not sure but i know this operator & to_proc, inject() method accept 2 args first accum second proc, but map() accept only one args proc or block. In inject() first args(accum) can be first item in enum.
It's just a special handling for this special case in some methods and lack of such handling in the others.

Meaning of & in parameters

I saw this code for a method same as each, except it receives a block to run some test against every item:
def every?(&predicate)
predicate = lambda { |item| item } if predicate.nil?
each do |item|
return false if !predicate.call(item)
end
true
end
Why is there a & in the parameter, and what does it do? What are the uses of it?
Sometimes in parameter lists you'll see something like
def foo(&block)
logic_with block
end
This just means that argument is expecting a block - and in your example.
&predicate just means passing a block as a parameter, which we're assigning to a local variable predicate
You can get a good idea of this from the fact that if predicate is nil the first line of the method assigns a new lamda to the predicate variable.
For further reading here's a good posts on blocks, procs and lambdas: http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/
EDITED per sawa's explanation below.
My take was you wanted the simple explanation that if you see & in this context it means a block is expected.
If you want to know specifically what the & operator itself actually does there's a good blog post here: http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-in-ruby/
As sawa mentions it's very similar to calling to_proc on the incoming block. From the post I linked to, in more detail:
if object is a block, it converts the block into a simple proc.
if object is a Proc, it converts the object into a block while preserving the lambda? status of the object.
if object is not a Proc, it first calls #to_proc on the object and then converts it into a block.
The two operators * and & swap Ruby objects and non-objects.
The operator * prepended to a list of comma-separated objects (which is not an object) converts it into an array (which is an object).
*("foo", "bar", "baz") # => ["foo", "bar", "baz"]
The operator * prepended to an object converts it into an array by applying to_a, and then into a list of comma-separated objects.
*["foo", "bar", "baz"] # => ("foo", "bar", "baz")
*nil # => *[] # => ()
The operator & prepended to a block (which is not an object) converts it into a proc (which is an object).
&{|e| puts e} # => ->(e){puts e}
The operator & prepended to an object converts it into a proc by applying to_proc, and then into a block.
&->(e){puts e} # => {|e| puts e}
&:foo # => &->(e){e.foo} # => {|e| e.foo}
When you have a & in an argument position, the & is appended to a block, so the third case above applies. The block becomes a proc.
In the context of a method definition, putting an ampersand in front of the last parameter indicates that a method may take a block and gives us a name to refer to this block within the method body.
I often refer to this post when I get confused.

Join doesn't convert the splat argument into a string in Ruby

Let's say we have this code:
def something(*someargs)
return *someargs.join(",")
end
Now, I found you can reference *someargs just like any other variable anywhere in the method definition. But I tried this...returning *someargs as a string, separated with a comma. Yet, when I call this method:
a = something(4, 5)
p a.class # => Array
p a #> ["4,5"]
why does something(4,5) still returns an array? If I do something like this:
[4, 5].join(",")
the result will be a string not in an array. So my question would be, how do I make the "something" method return an actual string which contains all the arguments as a string. And it's weird because if I do *someargs.class, the result is "Array", yet it doesn't behave like a typical array...
Try below :
def something(*someargs)
return someargs.join(",")
end
a = something(4, 5)
p a.class # => String
p a # => "4,5"
One example to explain your case:
a = *"12,11"
p a # => ["12,11"]
So when you did return *someargs.join(","), someargs.join(",") created the string as "4,5".But now you are using splat operator(*) again on the evaluated string "4,5" with the assignment operation like a = *"4,5". So finally you are getting ["4,5"].
Read more scenarios with splat operators here - Splat Operator in Ruby
Hope that helps.
An object prepended with a splat *... is not an object. You cannot reference such thing, nor can you pass it as an argument to a method because there is no such thing. However, if you have a method that can take multiple arguments, for example puts, then you can do something like:
puts *["foo", "bar"]
In this case, there is no such thing as *["foo", "bar"]. The splat operator is expanding it into multiple arguments. It is equivalent to:
puts "foo", "bar"
Regarding why someargs remains to be an array after someargs.join(","). That is because join is not a destructive method. It does not do anything to the receiver. Furthermore, an object cannot change its class by a destructive method. The only way to change the reference of someargs from an array to a string is to reassign it.

Passing a lambda as a block

I'm trying to define a block that I'll use to pass the the each method of multiple ranges. Rather than redefining the block on each range, I'd like to create a lamba, and pass the lambda as such:
count = 0
procedure = lambda {|v| map[count+=1]=v}
("A".."K").each procedure
("M".."N").each procedure
("P".."Z").each procedure
However, I get the following error:
ArgumentError: wrong number of arguments(1 for 0)
from code.rb:23:in `each'
Any ideas what's going on here?
Tack an ampersand (&) onto the argument, for example:
("A".."K").each &procedure
This signifies that you're passing it as the special block parameter of the method. Otherwise it's interpreted as a normal argument.
It also mirrors they way you'd capture and access the block parameter inside the method itself:
# the & here signifies that the special block parameter should be captured
# into the variable `procedure`
def some_func(foo, bar, &procedure)
procedure.call(foo, bar)
end
some_func(2, 3) {|a, b| a * b }
=> 6
The trick is in using an & which tells Ruby to convert this argument to a Proc if necessary and then use the object as the method’s block. Starting from Ruby 1.9 there's a shortcut for lambda (anonymous) functions. So, you can write code like this:
(1..5).map &->(x){ x*x }
# => [1, 4, 9, 16, 25]
will take each element of the array and compute its power
it is the same as this code:
func = ->(x) { x*x }
(1..5).map &func
for Ruby 1.8:
(1..5).map &lambda {|x| x*x}
# => [1, 4, 9, 16, 25]
To solve your problem you can use Array's method reduce (0 is initial value):
('A'..'K').reduce(0) { |sum,elem| sum + elem.size }
# => 11
Passing a lambda function to reduce is a bit tricky, but the anonymous block is pretty much the same as lambda.
('A'..'K').reduce(0) { |sum, elem| ->(sum){ sum + 1}.call(sum) }
# => 11
Or you could concat letters just like this:
('A'..'K').reduce(:+)
=> "ABCDEFGHIJK"
Convert to lowercase:
('A'..'K').map &->(a){ a.downcase }
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]
In the context of a method definition, putting an ampersand in front of the last parameter indicates that a method may take a block and gives us a name to refer to this block within the method body.
The other answers left something un-clarified that I'd like to expand on. How do we pass an arg and a block to a method?
Suppose we have a method that takes an arg and a block:
def method_with_arg_and_block(arg)
puts arg
yield
end
and a proc:
pr = proc { puts 'This is a proc'}
The answer: It's important that you pass the proc in as an arg with an ampersand rather than appending the proc to the method (like you would do with a block).
For example if you do:
method_with_arg_and_block('my arg') &pr
you will get a "no block given (yield)" exception.
The correct way to call this is:
method_with_arg_and_block('my arg', &pr)
Ruby will take care of converting the proc to a block and appending it to your method. Note: since lambdas are also procs, this will work with lambdas as well.
Thanks to https://medium.com/#sihui/proc-code-block-conversion-and-ampersand-in-ruby-35cf524eef55 for helping me understand this.

Passing a hash to a function ( *args ) and its meaning

When using an idiom such as:
def func(*args)
# some code
end
What is the meaning of *args? Googling this specific question was pretty hard, and I couldn't find anything.
It seems all the arguments actually appear in args[0] so I find myself writing defensive code such as:
my_var = args[0].delete(:var_name) if args[0]
But I'm sure there's a better way I'm missing out on.
The * is the splat (or asterisk) operator. In the context of a method, it specifies a variable length argument list. In your case, all arguments passed to func will be putting into an array called args. You could also specify specific arguments before a variable-length argument like so:
def func2(arg1, arg2, *other_args)
# ...
end
Let's say we call this method:
func2(1, 2, 3, 4, 5)
If you inspect arg1, arg2 and other_args within func2 now, you will get the following results:
def func2(arg1, arg2, *other_args)
p arg1.inspect # => 1
p arg2.inspect # => 2
p other_args.inspect # => [3, 4, 5]
end
In your case, you seem to be passing a hash as an argument to your func, in which case, args[0] will contain the hash, as you are observing.
Resources:
Variable Length Argument List, Asterisk Operator
What is the * operator doing
Update based on OP's comments
If you want to pass a Hash as an argument, you should not use the splat operator. Ruby lets you omit brackets, including those that specify a Hash (with a caveat, keep reading), in your method calls. Therefore:
my_func arg1, arg2, :html_arg => value, :html_arg2 => value2
is equivalent to
my_func(arg1, arg2, {:html_arg => value, :html_arg2 => value2})
When Ruby sees the => operator in your argument list, it knows to take the argument as a Hash, even without the explicit {...} notation (note that this only applies if the hash argument is the last one!).
If you want to collect this hash, you don't have to do anything special (though you probably will want to specify an empty hash as the default value in your method definition):
def my_func(arg1, arg2, html_args = {})
# ...
end

Resources