Recently I was having a discussion with a friend about Ruby's Proc. You can call a Proc in one of several ways. One way is to invoke Proc.call:
p = Proc.new { |x| "hello, #{x}" }
p.call "Bob"
=> "hello, Bob"
Another is to use braces, Proc.[]:
p ["Bob"]
=> "hello, Bob"
Are there any potential precedence issues here, or are these two statements completely interchangeable? If not, can you provide an example of a context where different results would be provided?
The #call technique allows the operator precedence to potentially obscure intent:
p = Proc::new do |a1| Proc::new do |a2| "#{a1.inspect}:#{a2.inspect}" end end
p.call([1,2,3]).call [1]
=> => "[1, 2, 3]:[1]"
p.call [1,2,3][1]
=> #<Proc:0x7ffa08dc#(irb):1>
p.call([1,2,3])[1]
=> "[1, 2, 3]:1"
p[[1,2,3]][[1]]
=> "[1, 2, 3]:[1]"
The [] syntax makes the syntactic association of the arguments to the method more robust, but you'd achieve the same effect by putting parentheses around the arguments to Proc#call.
Related
I need Ruby method to convert an array of strings into a Hash where each key is a string and each value is the 1-indexed index of the string in the original array.
hashify(%w(a b c))
# should return
{'a' => 1, 'b' => 2, 'c' => 3}
Even though I think I'm helping someone do their homework, I can't resist taking a golf swing, because Ruby is awesome:
%w(a b c).each.with_index(1).to_h
Also, defining "hashify" is VERY un-Ruby-like. I'd suggest alternatives, but it's probably a homework assignment anyways and you don't seem to want to learn it.
def hashify(array)
array.each.with_index(1).to_h
end
hashify(%w(a b c))
#=> { "a" => 1, "b" => 2, "c" => 3 }
There are (clearly) multiple ways you could achieve your goal in Ruby.
If you consider the expression %w(a b c).map.with_index(1).to_h, you can see that it is a matter of stringing together a few methods provided to us by the Enumerable class.
By first sending the :map message to the array, we receive back an Enumerator, which provides us the handy :with_index method. As you can see in the docs, with_index accepts an offset in an argument, so offsetting your indices by 1 is as simple as passing 1 as your argument to :with_index.
Finally, we call :to_h on the enumerator to receive the desired hash.
# fore!
def hashify(array)
array.map.with_index(1).to_h
end
> hashify %w(a b c)
=> {"a"=>1, "b"=>2, "c"=>3}
Try this, this will add a method to_hash_one_indexed to the Array class.
class Array
def to_hash_one_indexed
map.with_index(1).to_h
end
end
Then to call it:
%w(a b c).to_hash_one_indexed
#=> {"a"=>1, "b"=>2, "c"=>3}
My understanding is that a single splat on a non-array object calls to_a and then dissociates the elements apart. And since nil.to_a is defined to be [], the following conversion happens:
[:foo, *nil, :bar]
# => [:foo, *nil.to_a, :bar]
# => [:foo, *[], :bar]
# => [:foo, :bar]
By analogy, I thought that a double splat on a non-hash object calls to_h and then dissociates the key-value pairs apart. And since nil.to_h is defined to be {}, I expected the following conversion to happen:
{"foo" => 1, **nil, "bar" => 2}
# => {"foo" => 1, **nil.to_h, "bar" => 2}
# => {"foo" => 1, **{}, "bar" => 2}
# => {"foo" => 1, "bar" => 2}
But actually, it raises an error: no implicit conversion of nil into Hash. Why does it behave like that?
Edit I am not asking about the reasoning behind the design. I am asking where my thinking is wrong regarding double splat.
Well it's our human being super power to recognize patterns and predict things. However it's not always true. This is one example. Ruby is not consistent in splat and double splat. Your way of thinking is a good way to "remember" but it's not exactly the way Ruby works on splats.
See this bug report for more detail. In this bug report, Ruby's author Matz rather to remove the feature of being able to splat nil than add double splat to nil.
The reason *nil works is because the splat operator works on anything that responds to to_a, and nil.to_a returns []. The reason **nil doesn't work is that nil doesn't respond to to_hash, which is to_a's double-splat counterpart.
If you wanted this behavior, you could monkey-patch NilClass:
class NilClass
def to_hash
{}
end
end
{ "foo" => 1, **nil, "bar" => 2 }
# => { "foo" => 1, "bar" => 2 }
This question already has answers here:
Why is the splat used inside an array definition here?
(3 answers)
Closed 7 years ago.
I'm new to Ruby and trying to read through some code. I can't figure out what exactly the * symbol is telling the Ruby interpreter to do in this snippet:
[1]> items = [:one, :two, :three]
=> [:one, :two, :three]
[2]> x = Hash[*items.map { |item| [item, item.to_s+' !!'] }.flatten ]
=> {:one=>"one !!", :two=>"two !!", :three=>"three !!"}
Suppose I had some method in Ruby:
def a_method(a,b,c)
"#{a} #{b} #{c}"
end
and an array:
arr = [1,2,3]
I want to pass the 3 elements of the array into the method. This will produce an error:
a_method(arr) # wrong number of arguments
So what do I do? I could certainly do:
a_method(arr[0], arr[1], arr[2])
but there's an easier way, using the * "splat" operator:
a_method(*arr)
Basically, you'll achieve the same effect as above. Think of it like a "shortcut" that "converts" each array element into a method argument when being used when you call a method. This splat operator is a bit complicated to understand because it behaves differently when being used in different places (you have lots of useful articles on the topic).
In your example, basically, after the following expression is done:
items.map { |item| [item, item.to_s+' !!'] }.flatten
it produces:
[:one, "one !!", :two, "two !!", :three, "three !!"]
and this data is being passed to the Hash method using the "splat" operator, because Hash will not accept a single array as an argument:
arr = ['a', 'b', 'c', 'd']
p Hash{arr} #=> error, wrong number of arguments
p Hash[*arr] #=> {"a"=>"b", "c"=>"d"}
Without the *, you get
Hash[[:one, "one !!", :two, "two !!", :three, "three !!"]]
which won't work. The * "splats" the array into a sequence of arguments, giving you:
Hash[:one, "one !!", :two, "two !!", :three, "three !!"]
* in this context is the "splat" operator.
It's function is to turn an array into individual arguments to a function.
Given a function that accepts arguments...
def my_func(a, b, c)
end
You can invoke this function with three arguments by directly specifying arguments: my_func(1,2,3)
Or, if you have an array containing the arguments for the function, you can use the splat operator to "expand" the array to fill the arguments for the function:
args = [1,2,3]
my_func(*args) # identical to my_func(1,2,3)
In your particular case, there is a class method on Hash called [], which accepts a variable number of arguments. The code is using map to produce an array, and then passing each element of the array as an argument to Hash[].
Confusion with Array#each as below:
%w{ david black }.each {|str| str.capitalize }
#=> ["david", "black"]
The above code is cool,but how the below logic works,couldn't understand.
%w{ david black }.each(&:capitalize)
#=> ["david", "black"]
It's a very old trick, called Symbol#to_proc.
You can read more about it here: http://pragdave.pragprog.com/pragdave/2005/11/symbolto_proc.html
Basically, it's a shortcut for calling methods that take no args. Often used in map, for example.
%w[i can measure length of strings].map(&:length) # => [1, 3, 7, 6, 2, 7]
How about using map?
[1173]pry(main)> ["david","black"].map{|str| str.capitalize }
=> ["David","Black"]
[1173]pry(main)>
The notation of &:something does for every element of the array the method something.
This is usually used with map to change every entry of the array or to extract data from hashes.
[{:foo => :bar, :meh => :bar2}, {:foo => :one, :meh => :two}].map(&:foo)
=> [:bar, :one]
I don't get your example with .each, maybe you meant .map
When you pass in the &:method_name it's shorthand for doing it in the block. So for each item apply this method.
I've seen these several times but I can't figure out how to use them. The pickaxe says that these are special shortcuts but I wasn't able to find the syntactical description.
I've seen them in such contexts:
[1,2,3].inject(:+)
to calculate sum for example.
Let's start with an easier example.
Say we have an array of strings we want to have in caps:
['foo', 'bar', 'blah'].map { |e| e.upcase }
# => ['FOO', 'BAR', 'BLAH']
Also, you can create so called Proc objects (closures):
block = proc { |e| e.upcase }
block.call("foo") # => "FOO"
You can pass such a proc to a method with the & syntax:
block = proc { |e| e.upcase }
['foo', 'bar', 'blah'].map(&block)
# => ['FOO', 'BAR', 'BLAH']
What this does, is call to_proc on block and then calls that for every block:
some_object = Object.new
def some_object.to_proc
proc { |e| e.upcase }
end
['foo', 'bar', 'blah'].map(&some_object)
# => ['FOO', 'BAR', 'BLAH']
Now, Rails first added the to_proc method to Symbol, which later has been added to the ruby core library:
:whatever.to_proc # => proc { |e| e.whatever }
Therefore you can do this:
['foo', 'bar', 'blah'].map(&:upcase)
# => ['FOO', 'BAR', 'BLAH']
Also, Symbol#to_proc is even smarter, as it actually does the following:
:whatever.to_proc # => proc { |obj, *args| obj.send(:whatever, *args) }
This means that
[1, 2, 3].inject(&:+)
equals
[1, 2, 3].inject { |a, b| a + b }
inject accepts a symbol as a parameter, this symbol must be the name of a method or operator, which is this case is :+
so [1,2,3].inject(:+) is passing each value to the method specified by the symbol, hence summing all elements in the array.
source: https://ruby-doc.org/core-2.5.1/Enumerable.html