I'm learning about the unary operator, &.
There are some great questions about using & in the parameters of a method invocation. Usually the format goes something like some_obj.some_method(&:symbol):
Ruby unary operator & only valid on method arguments
What is the functionality of “&: ” operator in ruby?
What does map(&:name) mean in Ruby?
Unary Ampersand Operator and passing procs as arguments in Ruby
It seems like the main idea is ruby calls the to_proc method on :symbol when the unary operator is placed in front of the symbol. Because Symbol#to_proc exists "everything works".
I'm still confused about how everything just works.
What if I want to implement a "to_proc sort of functionality with a string". I'm putting it in quotes because I'm not really sure how to even talk about what I'm trying to do.
But the goal is to write a String#to_proc method such that the following works:
class String
def to_proc # some args?
Proc.new do
# some code?
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
This is how I did it:
class String
def to_proc
Proc.new do |some_arg|
parts = self.split(/ /)
some_proc = parts.first.to_sym.to_proc
another_arg = parts.last.to_i
some_proc.call(some_arg, another_arg)
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
The main part I'm confused about is how I get the parameters into the String#to_proc method. It seems like:
def to_proc
Proc.new do |some_arg| ...
end
Should be:
def to_proc some_arg
Proc.new do |yet_another_arg| ...
end
Or something like that. How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns?
Just write this
[2, 4, 6, 8].map { |each| each.to_s(2) }
Though I guess that is not what you're looking for …
Here is how Symbol#to_proc is implemented.
class Symbol
def to_proc
proc { |each| each.send(self) }
end
end
If you want you can define to_proc on an Array as follows
class Array
def to_proc
symbol, *args = self
proc { |each| each.send(symbol, *args) }
end
end
And then use
[2, 4, 6, 8].map(&[:to_s, 2])
Another alternative is using curry.
Though that does not work with bound methods, so you'll have to define a to_s lambda function first.
to_s = lambda { |n, each| each.to_s(n) }
[2, 4, 6, 8].map(&to_s.curry[2])
Though all of that seems more like academic exercises.
When you run some_method(&some_obj), Ruby first call the some_obj.to_proc to get a proc, then it "converts" that proc to a block and passes that block to some_method. So how the arguments go into the proc depends on how some_method passes arguments to the block.
For example, as you defined String#to_proc, which returns a proc{|arg| ...} (a proc with one argument), and calls [...].map(&'to_s 2'), Ruby interprets it as
[...].map(&('to_s 2'.to_proc))
which is
[...].map(&proc{|arg| ... })
and finally
[...].map {|arg| ... }
The problem with your approach is that there's no way to deduce the type of the argument when it's always passed as a string.
By the way, to address your question:
How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns?
They are some_arg here, which is not a variable you have to define but instead is a parameter that is automatically passed when the proc is called.
Here's a rewriting of the String patch and some usage examples:
class String
def to_proc
fn, *args = split ' '
->(obj) { obj.send(fn.to_sym, *args) }
end
end
This works for the following example:
p result = [[1,2,3]].map(&"join -")
# => ['1-2-3']
but fails for this (your example):
p result = [2, 4, 6, 8].map(&'to_s 2')
# => TypeError
The problem is to_s('2') is being called, when the 2 should be an integer, not a string. I can't think of any way to get around this except for maybe some serialization (although one of the other answers shows how eval can work).
Now that the limitations of this approach are clear, it's worth comparing it to the more commonly used patch on Symbol to enable argument passing to proc shorthands (this taken from can-you-supply-arguments-to-the-mapmethod-syntax-in-ruby)
class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]
This way you can pass any type of arguments to the proc, not just strings.
Once you've defined it, you can easily swap out String for Symbol:
class String
def call(*args, &blk)
to_sym.call(*args, &blk)
end
end
puts [1,2,3].map(&'+'.(1))
Refactored code
You're free to choose the name for the proc block variable. So it could be yet_another_arg, some_arg or something_else. In this case, the object you're passing to to_proc is actually the object you want to receive the proc call, so you could call it receiver. The method and param are in the String, so you get them with String#split from self.
class String
def to_proc
proc do |receiver|
method_name, param = self.split
receiver.method(method_name.to_sym).call(param.to_i)
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
# => ["10", "100", "110", "1000"]
Note that this method has been tailored to accept one method name and one integer argument. It doesn't work in the general case.
Another possibility
Warning
eval is evil
You've been warned
This works with the exact syntax you wanted, and it also works for a wider range of methods and parameters :
class String
def to_proc
proc { |x| eval "#{x.inspect}.#{self}" }
end
end
p [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
p ["10", "100", "110", "1000"].map(&'to_i 2')
#=> [2, 4, 6, 8]
p [1, 2, 3, 4].map(&'odd?')
#=> [true, false, true, false]
p %w(a b c).map(&'*3')
#=> ["aaa", "bbb", "ccc"]
p [[1,2,3],[1,2],[1]].map(&'map(&"*2")')
#=> [[2, 4, 6], [2, 4], [2]]
It also brings security problems, though. With great power comes great responsibility!
Related
I am just starting to do Groovy after mostly doing ruby.
It has a default 'block argument', it, as it were, not officially the terminology for Groovy, but I'm new to Groovy.
(1..10).each {println(it)}
What about Ruby? Is there a default I can use so I don't have to make |my_block_arg| every time?
Thanks!
No, you don't have a "default" in Ruby.
Though, you can do
(1..10).each(&method(:puts))
Like Andrey Deinekos answer explained there is no default. You can set the self context using BasicObject#instance_eval or BasicObject#instance_exec. I don't recommend doing this since it can sometimes result in some unexpected results. However if you know what you're doing the following is still an option:
class Enumerator
def with_ie(&block)
return to_enum(__method__) { each.size } unless block_given?
each { |e| e.instance_eval(&block) }
end
end
(1..10).each.with_ie { puts self }
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
#=> 1..10
(1..10).map.with_ie { self * self }
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
(-5..5).select.with_ie { positive? }
#=> [1, 2, 3, 4, 5]
If you want to call one method you might as well do (-5..5).select(&:positive?), but when the objects you're iterating over have actual attributes it might be worth the trouble. For example:
people.map.with_ie { "#{id}: #{first_name} - #{last_name}" }
Keep in mind that if you have an local variable id, first_name or last_name in scope those are used instead of the methods on the object. This also doesn't quite work for hashes or Enumerable methods that pass more than one block argument. In this case self is set to an array containing the arguments. For example:
{a: 1, b: 2}.map.with_ie { self }
#=> [[:a, 1], [:b, 2]]
{a: 1, b: 2}.map.with_ie { self[0] }
#=> [:a, :b]
From Ruby 2.7 onwards, you can use numbered block arguments:
(1..10).each { puts _1 }
Granted, this hasn't been very well documented; some references are still using #1, but the above is tested on the official 2.7 version.
I am trying to create a method for objects that I create. In this case it's an extension of the Array class. The method below, my_uniq, works. When I call puts [1, 1, 2, 6, 8, 8, 9].my_uniq.to_s it outputs to [1, 2, 6, 8].
In a similar manner, in median, I'm trying to get the reference of the object itself and then manipulate that data. So far I can only think of using the map function to assign a variable arr as an array to manipulate that data from.
Is there any method that you can call that gets the reference to what you're trying to manipulate? Example pseudo-code that I could replace arr = map {|n| n} with something like: arr = self.
class Array
def my_uniq
hash = {}
each do |num|
hash[num] = 0;
end
hash.keys
end
end
class Array
def median
arr = map {|n| n}
puts arr.to_s
end
end
Thanks in advance!
dup
class Array
def new_self
dup
end
def plus_one
arr = dup
arr.map! { |i| i + 1 }
end
def plus_one!
arr = self
arr.map! { |i| i + 1 }
end
end
array = [1, 3, 5]
array.new_self # => [1, 3, 5]
array.plus_one # => [2, 4, 6]
array # => [1, 3, 5]
array.plus_one! # => [2, 4, 6]
array # => [2, 4, 6]
dup makes a copy of the object, making it a safer choice if you need to manipulate data without mutating the original object. You could use self i.e. arr = self, but anything you do that changes arr will also change the value of self. It's a good idea to just use dup.
If you do want to manipulate and change the original object, then you can use self instead of dup, but you should make it a "bang" ! method. It is a convention in ruby to put a bang ! at the end of a method name if it mutates the receiving object. This is particularly important if other developers might use your code. Most Ruby developers would be very surprised if a non-bang method mutated the receiving object.
class Array
def median
arr = self
puts arr.to_s
end
end
[1,2,3].median # => [1,2,3]
In Ruby I'm trying to understand between the to_enum and enum_for methods. Before I my question, I've provided some sample code and two examples to help w/ context.
Sample code:
# replicates group_by method on Array class
class Array
def group_by2(&input_block)
return self.enum_for(:group_by2) unless block_given?
hash = Hash.new {|h, k| h[k] = [] }
self.each { |e| hash[ input_block.call(e) ] << e }
hash
end
end
Example # 1:
irb (main)> puts [1,2,3].group_by2.inspect
=> #<Enumerator: [1, 2, 3]:group_by2>
In example #1: Calling group_by on the array [1,2,3], without passing in a block, returns an enumerator generated with the command self.enum_for(:group_by_2).
Example #2
irb (main)> puts [1,2,3].to_enum.inspect
=> #<Enumerator: [1, 2, 3]:each>
In example #2, the enumerator is generated by calling the to_enum method on the array [1,2,3]
Question:
Do the enumerators generates in examples 1 and 2, behave differently in any way? I can see from the inspected outputs that they show slightly different labels, but I can find any difference in the enumerators' behavior.
# Output for example #1
#<Enumerator: [1, 2, 3]:each> # label reads ":each"
# Output for example #2
#<Enumerator: [1, 2, 3]:group_by2> # label reads ":group_by2"
p [1, 2, 3].to_enum
p [1, 2, 3].enum_for
--output:--
#<Enumerator: [1, 2, 3]:each>
#<Enumerator: [1, 2, 3]:each>
From the docs:
to_enum
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
...
enum_for
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
ruby is a language that often has method names that are synonyms.
Followup question:
Does the symbol in the command [1,2,3].to_enum(:foo) serve a purpose,
other than replacing :each with :foo in the output?
Yes. By default, ruby hooks up the enumerator to the receiver's each() method. Some classes do not have an each() method, for instance String:
str = "hello\world"
e = str.to_enum
puts e.next
--output:--
1.rb:3:in `next': undefined method `each' for "helloworld":String (NoMethodError)
from 1.rb:3:in `<main>
to_enum() allows you to specify the method you would like the enumerator to use:
str = "hello\nworld"
e = str.to_enum(:each_line)
puts e.next
--output:--
hello
Now, suppose you have the array [1, 2, 3], and you want to to create an enumerator for your array. An array has an each() method, but instead of creating an enumerator with each(), which will return each of the elements in the array, then end; you want to create an enumerator that starts over from the beginning of the array once it reaches the end?
e = [1, 2, 3].to_enum(:cycle)
10.times do
puts e.next()
end
--output:--
1
2
3
1
2
3
1
2
3
1
I have an array of integers
a = [3, 4, 5, 6]
and I need to POW this numbers, so they can be like this
a
# => [9, 16, 25, 36]
I'm trying to do this with this piece of code:
a.map!(&:**2)
but Isn't working :(
Can anyone help me?
You can use a lambda with & if you so desire:
square = lambda { |x| x**2 }
a.map!(&square)
This sort of thing is pointless busywork with a block so simple but it can be nice if you have a chain of such things and the blocks are more complicated:
ary.select(&some_complicated_criteria)
.map(&some_mangling_that_takes_more_than_one_line)
...
Collecting bits of logic in lambdas so that you can name the steps has its uses.
You should do this
a.map! { |i| i**2 }
Read the docs.
As a matter of rule, you cannot add parameters to methods using the &:sym syntax.
However, if you follow my suggestion here you could do the following:
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
a.map!(&:**.with(2))
# => [9, 16, 25, 36]
You can only use the &: shortcut syntax if you are calling a method on the object with no arguments. In this case, you need to pass 2 as an argument to the ** method.
Instead, expand the block to the full syntax
a.map! { |n| n**2 }
Given this irb session:
[2.0.0p195]> arr = [{count: 5}, {count: 6}, {count: 7}]
=> [{:count=>5}, {:count=>6}, {:count=>7}]
[2.0.0p195]> arr.collect(&:count)
=> [1, 1, 1]
wat
[2.0.0p195]> arr.collect(&:count).reduce(:+)
=> 3
[2.0.0p195]> arr.collect {|e| e[:count]}.reduce(:+)
=> 18
Can I exclude methods on Hash when collecting or is using a block the only way around this problem?
& means call #to_proc on its argument, and the Symbol class implements this by creating a Proc that calls the method name based on the symbol - so &:symbol means "Call the #symbol method on the passed in object". Essentially, what you've got is the equivalent of this:
arr.collect{|obj| obj.send(:count)}
Since Hash won't respond to the "count" method at all to get the value of the :count key - that is, Hash#count is not the same as Hash#[](:count), (though OpenStruct does do this for you), you're stuck with the block method.
Another alternative is to create a lambda, useful if you are writing the same block many times:
fetch_count = -> x{x[:count]}
arr.collect(&fetch_count) #=> [5, 6, 7]
# If hash only has one value as in example:
arr.collect(&values).flatten #=> [5, 6, 7]
The implementation of calling & on a symbol is as follows (more or less):
class Symbol
def to_proc
Proc.new { |obj| obj.send self }
end
end
You can see that all it is doing (when combined with a #map) is calling the method corresponding to the provided symbol on each member of the enumerable.
You could fix this if you really wanted by using OpenStructs instead of hashes, they have method-style access of elements:
[{test: 1}].map { |h| OpenStruct.new(h) }.map &:test
#=> [1]
Or invent an operator that does what you want for hash access in addition to &, I may revisit this challenge if I have a spare moment later!
EDIT: I have returned
This is hacky but you could monkey-patch symbol to provide the functionality that you wish for by augmenting with unary ~:
# Patch
class Symbol
def ~#
->(obj){ obj[self] }
end
end
# Example usage:
[{count: 5}, {count: 6}, {count: 7}].map &~:count
#=> [5, 6, 7]
If a free-for-all language such as Ruby doesn't have a feature that you wish for, you can always build it in :-)
Disclaimer: This is probably a terrible idea.