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

3

35

Ruby 2.5 introduced #yield_self method. Ruby 2.6 introduced #then method.

What is the difference between yield_self, yield(self), then and the existing Object#tap method?

Fuliginous answered 19/12, 2017 at 15:51 Comment(1)
This question will probably get some traffic, as more and more developers ponder this. +1 for usefulness, -1.5 for presentation. Please spend some time and make it good quality. (formatting, grammar, punctuation, some additional info, etc.). This can be an exemplary reference question.Lavaliere
C
63

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!!
Comedic answered 19/12, 2017 at 16:4 Comment(1)
Thanks for your explanation @ComedicFuliginous
S
15

Great summary of this here: Ruby 2.5 added yield_self.

Quick update: yield_self will have an alias then in new versions of Ruby, following requests from the community for this to be more readable.

It's a very good, concise read, quoted below for posterity. All credit to the original author, Vijay Kumar Agrawal:

Ruby 2.5 added a new method named yield_self. It yields the receiver to the given block and returns output of the last statement in the block.

irb> "Hello".yield_self { |str| str + " World" }
  => "Hello World"

How is it different from try in Rails ?

Without a method argument try behaves similar to yield_self. It would yield to the given block unless the receiver is nil and returns the output of the last statement in the block.

irb> "Hello".try { |str| str + " World" }
  => "Hello World"

Couple of differences to note are, try is not part of Ruby but Rails. Also try’s main purpose is protection against nil hence it doesn’t execute the block if receiver is nil.

irb> nil.yield_self { |obj| "Hello World" }
  => "Hello World"

irb> nil.try { |obj| "Hello World" }
  => nil

What about tap?

tap also is similar to yield_self. It’s part of Ruby itself. The only difference is the value that is returned. tap returns the receiver itself while yield_self returns the output of the block.

irb> "Hello".yield_self { |str| str + " World" }
  => "Hello World"

irb> "Hello".tap { |str| str + " World" }
  => "Hello"

Overall, yield_self improves readability of the code by promoting chaining over nested function calls. Here is an example of both the styles.

irb> add_greeting = -> (str) { "HELLO " + str }
irb> to_upper = -> (str) { str.upcase }

# with new `yield_self`
irb> "world".yield_self(&to_upper)
            .yield_self(&add_greeting)
  => "HELLO WORLD"

# nested function calls
irb> add_greeting.call(to_upper.call("world"))
  => "HELLO WORLD"

yield_self is part of Kernel and hence it’s available to all the objects.

Please don't accept this as the answer, as it's not my own handiwork (and I'm happy to delete if anyone has any objections) - but I found this a very good read and thought it might help others at some point.

Sindysine answered 19/12, 2017 at 15:51 Comment(4)
That reference is helpful, but a link in a comment would be sufficient and appropriate.Finella
Cheers @CarySwoveland, I did think that, though the number of times I've followed a dead link meant me think it might be handy to be able to refer to this directly. As said, happy to remove if the consensus is for that.Sindysine
I'd want this to stay. @SRack: you could transform this answer into a community wiki, so that you don't benefit from the upvotes on the content you didn't produce :)Lavaliere
Good idea @SergioTulentsev - didn't know of that option. I've switched it over now. Thanks for the advice!Sindysine
C
5

I would like to add to mikej's answer on parallelism between each vs. map and tap vs. yield_self.

There are many question asking the difference between each and map for enumerables. The former is used for doing something with a side effect or something destructive, and then returning the receiver, which is handy for chaining methods. The latter returns the evaluated value in place of each element in the receiver.

tap vs. yield_self is just like this; the difference is that they are done for a single receiver object instead of elements within an enumerable receiver. So their use case would parallel what I wrote above. The former is used for doing something with a side effect or something destructive, and then returning the receiver, which is handy for chaining methods. The latter returns the evaluated value instead of the receiver.

Chammy answered 20/12, 2017 at 3:9 Comment(1)
Interesting perspective, thanks for sharing. Just like map can be more functional and elegant than each and I would use each less often, then can be more functional and elegant than tap and I would use tap less often (if at all).Dislocation

© 2022 - 2024 — McMap. All rights reserved.