danielewski.dev

yield_self in Ruby

One of the new feature of Ruby 2.5 is the Kernel#yield_self method. Let’s take a look at how it works.

From the documentation:

Yields self to the block and returns the result of the block.

This can be very useful when chaining methods together. It reminds the pipe operator from Elixir.

Elixir and |>:

# Before
foo(bar(baz(42)))

# After
42 |> baz |> bar |> foo

Ruby’s yield_self and procs:

Foo = proc { |n| n * n }
Bar = proc { |n| n + 1 }
Baz = proc { |n| n - 1 }

# Before
Foo.call(Bar.call(Baz.call(42)))

# After
42.yield_self(&Baz)
  .yield_self(&Bar)
  .yield_self(&Foo)

Ruby’s yield_self and methods:

def foo n; n * n; end
def bar n; n + 1; end
def baz n; n - 1; end

# Before
foo(bar(baz(42)))

# After
42.yield_self(&method(:baz))
  .yield_self(&method(:bar))
  .yield_self(&method(:foo))

Ruby is obviously more verbose. Syntactic sugar may come up in the future.

Let’s continue with another example.

Before:

url = 'https://api.coindesk.com/v1/bpi/currentprice.json'
uri = URI.parse(url)
body = Net::HTTP.get(uri)
json = JSON.parse(body)
rate = json.fetch('bpi').fetch('USD').fetch('rate')

After:

url = 'https://api.coindesk.com/v1/bpi/currentprice.json'
rate = url.yield_self { |url| URI.parse(url) }
          .yield_self { |uri| Net::HTTP.get(uri) }
          .yield_self { |body| JSON.parse(body) }
          .yield_self { |json| json.fetch('bpi').fetch('USD').fetch('rate') }

Again, it looks more verbose but it removes the need for temporary variables (url, uri, body and json).

Finally, yield_self may look similar to Object#tap:

Yields self to the block, and then returns self.

The only difference is the returned value. Object#tap returns self whereas Kernel#yield_self returns the result of the block.

42.tap { |n| n*n } # => 42
42.yield_self { |n| n*n } # => 1764