Advantage of Tap Method in Ruby

advantage of tap method in ruby

When readers encounter:

user = User.new
user.username = "foobar"
user.save!

they would have to follow all the three lines and then recognize that it is just creating an instance named user.

If it were:

user = User.new.tap do |u|
u.username = "foobar"
u.save!
end

then that would be immediately clear. A reader would not have to read what is inside the block to know that an instance user is created.

Understanding tap in Ruby

.tap is here to "perform operations on intermediate results within a chain of methods" (quoting ruby-doc).

In other words, object.tap allows you to manipulate object and to return it after the block:

{}.tap{ |hash| hash[:video] = 'Batmaaaaan' }
# => return the hash itself with the key/value video equal to 'Batmaaaaan'

So you can do stuff like this with .tap:

{}.tap{ |h| h[:video] = 'Batmaaan' }[:video]
# => returns "Batmaaan"

Which is equivalent to:

h = {}
h[:video] = 'Batmaaan'
return h[:video]

An even better example:

user = User.new.tap{ |u| u.generate_dependent_stuff }
# user is equal to the User's instance, not equal to the result of `u.generate_dependent_stuff`

Your code:

def self.properties_container_to_object(properties_container)
{}.tap do |obj|
obj['vid'] = properties_container['vid'] if properties_container['vid']
obj['canonical-vid'] = properties_container['canonical-vid'] if properties_container['canonical-vid']
properties_container['properties'].each_pair do |name, property_hash|
obj[name] = property_hash['value']
end
end
end

Is returning a Hash beeing filled in the .tap block

The long-version of your code would be:

def self.properties_container_to_object(properties_container)
hash = {}

hash['vid'] = properties_container['vid'] if properties_container['vid']
hash['canonical-vid'] = properties_container['canonical-vid'] if properties_container['canonical-vid']
properties_container['properties'].each_pair do |name, property_hash|
hash[name] = property_hash['value']
end

hash
end

what ruby tap method does on a {}

#tap method simply passes an object it was called on to a block. At the end of the block it returns the same object again. This way you can chain operations or restrict variable scope.

{}.tap { |h| h[:a] = 1 }.size # => 1

You were able to chain a next method to this block. And also avoided creating a h variable in your scope.

Combinatory method like tap, but able to return a different value?

Define Object#as:

class Object
def as
yield self
end
end

And now you can write:

def not_sure_this_is_nice_enough_method1
something_complex(a, b, c).as do |obj|
a_predicate_check? ? obj.one_more_method_call : obj
end
end

Ruby Tempfile#tap: What class defines this method and what is it for?

#tap is defined on Object

https://ruby-doc.org/core-2.3.1/Object.html#method-i-tap

It was introduced in Ruby 1.9. It yields self to the block and then returns self. I think an illustrative example is when it's used to return an object from a method.

You could do this.

def foo
a = []
a.push(3)
a
end

def foo
[].tap do |a|
a.push(3)
end
end

In the first example the array a is returned explicitly and in the second tap is being used to yield the block to self and then return self.

Difference between Kernel#yield_self, yield(self), Kernel#then and Object#tap in Ruby?

The difference between tap and yield_self is in what is returned by each of the two methods.

Object#tap yields self to the block and then returns self. Kernel#yield_self yields self to the block and then returns the result of the block.

Here are some examples of where each can be useful:

tap

Replacing the need for a result line at the end of a method:

def my_method
result = get_some_result
call_some_method_with result
result
end

can be written as:

def my_method
get_some_result.tap do |result|
call_some_method_with result
end
end

Another example is initialisation of some object that takes several steps:

some_object = SomeClass.new.tap do |obj|
obj.field1 = some_value
obj.field2 = other_value
end

yield_self and yield(self)

If used inside one of your own methods yield_self would have the same effect as yield(self). However, by having it as a method in its own right this promotes method chaining as an alternative to nested function calls.

This blog post by Michał Łomnicki has some helpful examples. e.g. this code:

CSV.parse(File.read(File.expand_path("data.csv"), __dir__))
.map { |row| row[1].to_i }
.sum

can be rewritten as:

"data.csv"
.yield_self { |name| File.expand_path(name, __dir__) }
.yield_self { |path| File.read(path) }
.yield_self { |body| CSV.parse(body) }
.map { |row| row[1].to_i }
.sum

This can aid with clarity where nested calls are being used for a series of transformations on some data. Similar features exist in other programming languages. The pipe operator in Elixir is a good one to take a look at.

then

#yield_self might feel a bit technical and wordy. That's why Ruby 2.6 introduced an alias for #yield_self, #then.

"something"
.then {|str| str.chars.map {|x| x.ord + 1 }}
.then {|ords| ords.map {|x| x.chr }}
.then {|chars| chars.join }
.then {|str| str + "!" }
.then {|str| str + "!" }
# tpnfuijoh!!

Are there any advantages in using block initialization?

The only reason here is to have more concise code(you don't have to add the instance name before the method call). By using blocks in general you can write very neat, concise and readable code. Some times you can save your code consumers lots of typing and even code logic.
Here is a traditional case!

file = File.open("some_file.txt","w")
file << "more code"
file.close

Compare it to this nice block alternative:

File.open("some_file.txt","w") { |file| file << "adding new stuff" }

It saved the user from the hassle of having to open and close(personally i keep forgetting this one) the file himself. Instead it made him focus on what he wants.

Try to invest blocks in such situations + when you want to write nice DSLs.

The advantage of enumerators over collections

Using an Enumerator makes some things possible that Arrays don't allow.

An Enumerator can represent a sequence that is lazily evaluated, meaning the code to generate an element doesn't get run until the element is needed.

That means that an Enumerator can represent an infinite sequence. For example:

e = Enumerator.new { |y| s = 'a'; loop { y << s = s.succ } }
e.first(5) # => ["b", "c", "d", "e", "f"]

This example shows an infinite sequence, which implies it has to be lazily evaluated because your computer is finite. But even if you aren't working with infinite sequences, the lazy evaluation can still be very helpful for saving CPU time.

Also, an Enumerator can be used an external iterator, which provides all sorts of flexibility in how you use it. (See 7stud's answer for an example.)



Related Topics



Leave a reply



Submit