What about implicit yield in Ruby? [duplicate] - ruby

This question already has answers here:
Can you supply arguments to the map(&:method) syntax in Ruby?
(9 answers)
Closed 8 years ago.
I often write:
some_array.each { |array_element| array_element.some_method(args) }
Why not have an option for an implicit yield so you could write e.g.:
some_array.each { _.some_method(args) }
I am not sure what character _ should actually be, and I imagine it would only be used in the most boilerplate setting, where you're dealing with a one-dimensional array and just trying to yield each item to the block in succession. It would save a lot of redundant typing.

Are you familiar with #to_proc and the & syntax for method calls? They cover some cases similar to the one you show here. For example:
[1, -2, -4].map(&:abs) => [1, 2, 4]
The & is used to pass an object in place of a block. If the object isn't a Proc, #to_proc is automatically called on it to get a Proc before it is used in place of the block. Symbol#to_proc returns a Proc which behaves like: { |obj, *args| obj.symbol_name(*args) }.
In your example, you use args which are presumably captured from the surrounding lexical environment. Symbol#to_proc won't help you there. But it wouldn't be hard to make a new Proc-building method which would. For example:
class Symbol
def with_args(*args)
Proc.new { |x| x.send(self, *args) }
end
end
Then you could do:
some_array.each(&:some_method.with_args(args))
Whether that is any better than an explicit block is up to you. In any case, this is a technique you should be aware of.

Related

Ruby passing method

Trying to understand ruby's complexity, but makes no sense so far.
5.times(method(:puts))
Gives error, that doesn't make a lot of sense. Do I have some kind of syntax error or is it not possible to do in ruby?
ArgumentError: wrong number of arguments (given 1, expected 0)
from (irb):78:in `times'
I am trying to do something similar to
[0, 1, 2, 3, 4].forEach(console.log)
and
java.util.stream.IntStream.range(0, 5).forEach(System.out::println);
At the same time these do work:
method(:puts).call(1)
# and
5.times { |i| puts i }
times takes a block argument, which is distinguished from "regular" arguments by an ampersand. You can either pass it an explicit block
5.times { |x| puts x }
or you can pass it a value with &
5.times(&method(:puts))
Having the block argument treated differently allows us to write methods that look and act a lot like built-in statements. For instance, an infinite loop in Ruby can be written as
loop {
# fun stuff happening in here
}
But loop is a method in the core library, not a built-in keyword. We could've written that loop function ourselves. Enumerable and other modules make heavy use of block arguments to provide friendlier syntax, which is one of the primary goals of Ruby as a language.

Ruby - get method params names and values [duplicate]

This question already has answers here:
Is there a way to access method arguments in Ruby?
(11 answers)
Closed 5 years ago.
Is there a way to get method parameters names and values dynamically, maybe a metaprogramming?
def my_method(name, age)
# some code that solves this issue
end
my_method('John', 22) # => { name: 'John', age: 22 }
I want to use this in all my methods in order to log methods calls and respective parameters, but without to do this manually in every method.
Thanks.
Yup! In ruby, it's called the binding, which is an object that encapsulates the context in which a particular line runs. The full docs are here, but in the case of what you're trying to do...
def my_method(arg1, arg2)
var = arg2
p binding.local_variables #=> [:arg1, :arg2, :var]
p binding.local_variable_get(:arg1) #=> 1
p Hash[binding.local_variables.map{|x| [x, binding.local_variable_get(x)]}] #=> {:arg1 => 1, :arg2 => 2, :var => 2}
end
my_method(1, 2)
I'd strongly advise against Binding#eval, if you can possibly help it. There's almost always a better way to sovle problems than by using eval. Be aware that binding encapsulates context on the line at which it is called, so if you were hoping to have a simple log_parameters_at_this_point method, you'll either need to pass the binding into that method, or use something cleverer like binding_of_caller

Understanding `array.map(&:method)` [duplicate]

This question already has answers here:
What does map(&:name) mean in Ruby?
(17 answers)
Closed 6 years ago.
Why does:
[1,2,3,4,5].map(&:to_s) #=> ["1", "2", "3", "4", "5"]
work but:
[1,2,3,4,5].map(&:*(2))
throws an unexpected syntax error?
& is called the to_proc operator. It calls the to_proc method on the expression that follows it and then passes the resulting Proc to the method as a block.
In the case of &:to_s, :to_s is a Symbol, so it the operator calls Symbol#to_proc. The docs are a little garbled, but suffice it to say that these two expressions are more-or-less equivalent:
my_proc = :to_s.to_proc
my_proc = Proc.new {|obj| obj.to_s }
So the answer to the question "Why doesn't &:*(2) work?" is that the expression that follows the & operator, :*(2), isn't a valid Ruby expression. It makes about as much sense to the Ruby parser as "hello"(2).
There is, by the way, a way to do what you're trying to do:
[1,2,3,4,5].map(&2.method(:*))
# => [2, 4, 6, 8, 10]
In the above code, 2.method(:*) returns a reference to the * method of the object 2 as a Method object. Method objects behave a lot like Proc objects, and they respond to to_proc. However, the above isn't exactly equivalent—it does 2 * n rather than n * 2 (a distinction that doesn't matter if n is also a Numeric)—and it's not any more succinct or readable than {|n| n * 2 }, and so rarely worth the trouble.
Ampersand and object (&:method)
The & operator can also be used to pass an object as a block to a method, as in the following example:
arr = [ 1, 2, 3, 4, 5 ]
arr.map { |n| n.to_s }
arr.map &:to_s
Both the examples above have the same result. In both, the map method takes the arr array and a block, then it runs the block on each element of the array. The code inside the block runs to_s on each element, converting it from integers to strings. Then, the map method returns a new array containing the converted items.
The first example is common and widely used. The second example may look a bit cryptic at first glance. Let's see what's happening:
In Ruby, items prefixed with colon (:) are symbols. If you are not familiar with the Symbol class/data type, I suggest you Google it and read a couple of articles before continuing. All method names in Ruby are internally stored as symbols. By prefixing a method name with a colon, we are not converting the method into a symbol, neither are we calling the method, we are just passing the name of the method around (referencing the method). In the example above, we are passing :to_s, which is a reference to the to_s method, to the ampersand (&) operator, which will create a proc (by calling to_proc under the hood). The proc takes a value as an argument, calls to_s on it and returns the value converted into a string.
Although the :to_s symbol is always the same, when running the map loop, it will refer to the to_s method of the class corresponding to each array item. If we passed an array such as [ 21, 4.453, :foobar, ] to the map method, the to_s method of the Fixnum class would be applied (called) on the first item, the to_s method of the Float class would be applied to the second item and the to_s method of the Symbol class would be applied to the third item. This makes sense because we are not passing the actual to_s method to the ampersand operator, just its name.
Below is an example of creating a proc that takes an argument, calls a method on it and returns the result of the method.
p = :upcase.to_proc
p.call("foo bar")
Output:
=> "FOO BAR"
Let's review what is going on in arr.map &:to_s
At each iteration of map, one item of the array (an integer) is passed to &:to_s
The :to_s symbol (which is a reference to the to_s method) is passed to the & operator, which creates a proc that will take an argument (an array item), call to_s on the argument and return the value converted into string;
The map method returns a new array containing the strings "1", "2", "3", "4" and "5".

Ruby method chaining - `tap` with replacement [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a `pipe` equivalent in ruby?
I'm looking at the tap method in Ruby - but unfortunately the object returned from the passed block is not passed on. What method do I use to have the object passed on?
Here's what I'm trying to (unsuccessfully) do:
obj.tap{ |o| first_transform(o) }.tap{ |o| second_transform(o)}
This is, of course, equivalent to second_transform(first_transform(o)). I'm just asking how to do it the first way.
Doing this is trivial with lists:
list.map{ |item| first_transform(item) }.map{ |item| second_transform(item)}
Why isn't it as easy with objects?
class Object
def as
yield self
end
end
With this, you can do [1,2,3].as{|l| l << 4}.as{|l| l << 5}
You could also consider to make #first_transform and #second_transform instance methods of the item's class (and return the transformed item of course).
These methods definitions should look like this:
class ItemClass
# If you want your method to modify the object you should
# add a bang at the end of the method name: first_transform!
def first_transform
# ... Manipulate the item (self) then return it
transformed_item
end
end
This way you could simply chain the methods calls this way:
list.map {|item| item.first_transform.second_transform }
It even reads better in my humble opinion ;)
The simple answer is tap doesn't do what you think it does.
tap is called on an object and will always return that same object.
As a simple example of taps use
"foo".tap do |foo|
bar(foo)
end
This still returns "foo"
In your example you have an object, and you want to apply two functions to it in succession.
second_transform(first_transform(obj))
UPDATED:
So I guess I'd ask why you want to chain in this way.
obj.do{|o| first_transform(o)}.do{|o| second_transform(o)}
Is that really more clear than
second_transform(first_transform(obj))
Lets take an example I often use
markdown(truncate(#post.content))
or
truncated_post = truncate(#post.content)
markdown(truncated_post)
I guess it depends on the nature of your transform

How to pass a function instead of a block [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Shorter way to pass every element of an array to a function
I know this will work:
def inc(a)
a+1
end
[1,2,3].map{|a| inc a}
but in Python, I just need to write:
map(inc, [1,2,3])
or
[inc(x) for x in [1,2,3])
I was wondering whether I can skip the steps of making a block in Ruby, and did this:
[1,2,3].map inc
# => ArgumentError: wrong number of arguments (0 for 1)
# from (irb):19:in `inc'
Does anyone have ideas about how to do this?
According to "Passing Methods like Blocks in Ruby", you can pass a method as a block like so:
p [1,2,3].map(&method(:inc))
Don't know if that's much better than rolling your own block, honestly.
If your method is defined on the class of the objects you're using, you could do this:
# Adding inc to the Integer class in order to relate to the original post.
class Integer
def inc
self + 1
end
end
p [1,2,3].map(&:inc)
In that case, Ruby will interpret the symbol as an instance method name and attempt to call the method on that object.
The reason you can pass a function name as a first-class object in Python, but not in Ruby, is because Ruby allows you to call a method with zero arguments without parentheses. Python's grammar, since it requires the parentheses, prevents any possible ambiguity between passing in a function name and calling a function with no arguments.
Does not answer your question but if you really just want to increment all your variables, you have Integer#next
4.next
#=> 5
[1,2,3].map(&:next)
#=> [2, 3, 4]

Resources