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.
Related
I am aware of the shorthand for map that looks like:
[1, 2, 3, 4].map(&:to_s)
> ["1", "2", "3", "4"]
I was told this is shorthand for:
[1, 2, 3, 4].map{|i| i.to_s}
This makes perfect sense. My question is this: It seems there should be an easier way to write:
[1, 2, 3, 4].map{|x| f.call(x)}
for some procedure f. I know the way I just typed isn't all that long to begin with, but I'd contend that neither is the previous example for which the shorthand exists. This example just seems like the complement to the first example: Rather than calling i's to_s method for every i, I wish to call f for every x.
Does such a shorthand exist?
Unfortunately this shorthand notation (which calls "Symbol#to_proc") does not have a way to pass arguments to the method or block being called, so you couldn't even do the following:
array_of_strings.map(&:include, 'l') #=> this would fail
BUT, you are in luck because you don't actually need this shortcut to do what you are trying to do. The ampersand can convert a Proc or Lambda into a block, and vice-versa:
my_routine = Proc.new { |str| str.upcase }
%w{ one two three }.map &my_routine #=> ['ONE', 'TWO', 'THREE']
Note the lack of the colon before my_routine. This is because we don't want to convert the :my_routine symbol into a proc by finding the method and calling .method on it, rather we want to convert the my_routine Proc into a block and pass it to map.
Knowing this, you can even map a native Ruby method:
%w{ one two three }.map &method(:p)
The method method would take the p method and convert it into a Proc, and the & converts it into a block that gets passed to map. As a result, each item gets printed. This is the equivalent of this:
%w{ one two three }.map { |s| p s }
As of Ruby 2.7, numbered parameters (_1, _2, etc) are supported. This syntax can be cryptic, especially with more than one parameter, but it can be useful for simple situations.
[1, 2, 3, 4].map { f.call(_1) }
If I have a method with a parameter list like such:
def func(param1, param2, param3, para4, param5, param6)
what would be the best way to get rid of the long list? There are suggestion to create a separate object, but I'm not sure how I would do that.
You can use the variadic * splat in Ruby:
def f(*p)
p1,p2,p3,p4,p5,p6,p7,p8=*p
p p
p p8
end
f(*(1..8).to_a)
# [1, 2, 3, 4, 5, 6, 7, 8]
# 8
There are tradeoffs here. You loose the interpreter's help in counting the args actually passed to the method.
By doing:
p1,p2,p3,p4,p5,p6,p7,p8=*p
any arguments more than 8 to the method will be silently ignored and if less than 8, the shortfall will be silently assigned nil:
> f(1,2,3)
[1, 2, 3]
nil
If that is OK, the * is a useful shortcut.
You can also use an empty array as a default arg:
def f2(p=[])
p1,p2,p3,p4,p5,p6,p7,p8=*p
p p
p p8
end
Then call without the splat since now the method is expecting one argument, not a variable number:
f2(1..8)
With the same comment regarding the array length apply: arguments lengths greater than 8 are silently ignored and less than 8 are silently assigned nil
You can add argument checking to variable length arguments if desired:
def f(*p)
args_expected=8
raise ArgumentError, "\"#{__method__}\": wrong number of arguments (given %d expected %d)" % [p.length, args_expected] if p.length !=args_expected
p1,p2,p3,p4,p5,p6,p7,p8=*p
end
> f(1,2,3)
ArgumentError: "f": wrong number of arguments (given 3 expected 8)
But now you are adding words and complexity back.
There are good reasons to use named positional arguments if you are expecting a fixed number of arguments. The function is easy to change and you are allowing the interpreter help you.
A suggestion would be to use a single hash with the parameters as keys/options.
def func(hash_param = {})
param1 = hash_param[:param1]
param2 = hash_param[:param2]
param3 = hash_param[:param3]
param4 = hash_param[:param4]
param5 = hash_param[:param5]
param6 = hash_param[:param6]
#rest_of_code
end
hash = {
param1: 'foo',
param2: 'bar',
param3: 'baz',
param4: 'qux',
param5: 'alice',
param6: 'bob'
}
func(hash)
If some specific keys inside the hash are not defined, they will simply return nil.
I understand that I can pass a method to inject. For instance,
[1,2,3].inject(:+) #=> 6
but this one throws
["1","2","3"].inject(:to_i) #=> TypeError: no implicit conversion of String into Integer
["1","2","3"].inject(:to_s) #=> ArgumentError: wrong number of arguments (1 for 0)
I am not doing anything particular, just trying to get my basics right.
The short explanation is "the callback for inject has to take two arguments." But that probably won't clear it up entirely.
OK, so let's look at the normal block form of inject:
[1, 2, 3].inject {|memo, number| memo + number}
Passing a symbol works the same way — it just turns the symbol into a proc that takes the place of the block. When you turn a symbol into a proc, the conversion looks like this:
class Symbol
def to_proc
proc {|receiver, *args| receiver.send(self, *args) }
end
end
So when you pass :+, it calls the + method of the memo value with the current number as the argument, just like 1 + 2.
So when you pass :to_i, it's equivalent to this:
["1", "2", "3"].inject {|memo, number_string| memo.to_i(number_string) }
But that doesn't make any sense. You're trying to pass the string as an argument to to_i. which is invalid.
I am learning rails and following this thread. I am stuck with the to_proc method. I consider symbols only as alternatives to strings (they are like strings but cheaper in terms of memory). If there is anything else I am missing for symbols, then please tell me. Please explain in a simple way what to_proc means and what it is used for.
Some methods take a block, and this pattern frequently appears for a block:
{|x| x.foo}
and people would like to write that in a more concise way. In order to do that they use a combination of: a symbol, the method Symbol#to_proc, implicit class casting, and & operator. If you put & in front of a Proc instance in the argument position, that will be interpreted as a block. If you combine something other than a Proc instance with &, then implicit class casting will try to convert that to a Proc instance using to_proc method defined on that object if there is any. In case of a Symbol instance, to_proc works in this way:
:foo.to_proc # => ->x{x.foo}
For example, suppose you write:
bar(&:foo)
The & operator is combined with :foo, which is not a Proc instance, so implicit class cast applies Symbol#to_proc to it, which gives ->x{x.foo}. The & now applies to this and is interpreted as a block, which gives:
bar{|x| x.foo}
The easiest way to explain this is with some examples.
(1..3).collect(&:to_s) #=> ["1", "2", "3"]
Is the same as:
(1..3).collect {|num| num.to_s} #=> ["1", "2", "3"]
and
[1,2,3].collect(&:succ) #=> [2, 3, 4]
Is the same as:
[1,2,3].collect {|num| num.succ} #=> [2, 3, 4]
to_proc returns a Proc object which responds to the given method by symbol.
So in the third case, the array [1,2,3] calls its collect method and. succ is method defined by class Array. So this parameter is a short hand way of saying collect each element in the array and return its successor and from that create a new array which results in [2,3,4]. The symbol :succ is being converted to a Proc object so it call the Array's succ method.
For me the clearest explanation is seeing a simple implementation of it. Here's what it might look like if I were reimplementing Symbol#to_proc:
class Symbol # reopen Symbol class to reimplement to_proc method
def to_proc
->(object) { object.send(self) }
end
end
my_lambda = :to_s.to_proc
puts my_lambda.(1) # prints '1'; .() does the same thing as .call()
puts my_lambda.(1).class # prints 'String'
puts [4,5,6].map(&:to_s) # prints "4\n5\n6\n"
puts [4,5,6].map(&:to_s).first.class # prints 'String'
For anybody still a bit stumped, running the following code might make things a little clearer:
class Symbol
def to_proc
proc do |obj|
puts "Symbol proc: #{obj}.send(:#{self})"
obj.send(self)
end
end
end
class Array
def map(&block)
copy = self.class.new
self.each do |index|
puts "Array.map: copy << block.call(#{index})"
copy << block.call(index)
end
copy
end
end
remapped_array = [0, 1, 2].map &:to_s
puts "remapped array: #{remapped_array.inspect}"
These are not the actual implementations of Symbol.to_proc or Array.map, they are just simplified versions which I'm using to demonstrate how map &:to_s and similar calls work.
I am aware of the shorthand for map that looks like:
[1, 2, 3, 4].map(&:to_s)
> ["1", "2", "3", "4"]
I was told this is shorthand for:
[1, 2, 3, 4].map{|i| i.to_s}
This makes perfect sense. My question is this: It seems there should be an easier way to write:
[1, 2, 3, 4].map{|x| f.call(x)}
for some procedure f. I know the way I just typed isn't all that long to begin with, but I'd contend that neither is the previous example for which the shorthand exists. This example just seems like the complement to the first example: Rather than calling i's to_s method for every i, I wish to call f for every x.
Does such a shorthand exist?
Unfortunately this shorthand notation (which calls "Symbol#to_proc") does not have a way to pass arguments to the method or block being called, so you couldn't even do the following:
array_of_strings.map(&:include, 'l') #=> this would fail
BUT, you are in luck because you don't actually need this shortcut to do what you are trying to do. The ampersand can convert a Proc or Lambda into a block, and vice-versa:
my_routine = Proc.new { |str| str.upcase }
%w{ one two three }.map &my_routine #=> ['ONE', 'TWO', 'THREE']
Note the lack of the colon before my_routine. This is because we don't want to convert the :my_routine symbol into a proc by finding the method and calling .method on it, rather we want to convert the my_routine Proc into a block and pass it to map.
Knowing this, you can even map a native Ruby method:
%w{ one two three }.map &method(:p)
The method method would take the p method and convert it into a Proc, and the & converts it into a block that gets passed to map. As a result, each item gets printed. This is the equivalent of this:
%w{ one two three }.map { |s| p s }
As of Ruby 2.7, numbered parameters (_1, _2, etc) are supported. This syntax can be cryptic, especially with more than one parameter, but it can be useful for simple situations.
[1, 2, 3, 4].map { f.call(_1) }