Project

General

Profile

Feature #14594

Rethink yield_self's name

Added by zverok (Victor Shepelev) about 1 month ago. Updated about 12 hours ago.

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:86270]

Description

I feel really uncomfortable raising the question again, but...

In several months since 2.5 release I've written a lot of code with yield_self (using backports gem with earlier versions of Ruby when necessary), and explained it several times to students, and colleagues (and in this blog post which have gained pretty decent attention).

I should say that I am still assured the name chosen is really not optimal. Reasons:

  • it is just too long for such a basic operation;
  • it does not say "what it does", but rather "how it is implemented"; it is like having each_returning_block_result instead of map;
  • self is really misguiding and obscure in situations like this:
class MyClass
  def some_method
    @path.yield_self(&File.method(:read)).yield_self(&Parser.method(:new)) ...
  end
end

Intuitively, word "self" inside instance method is read like it somehow related to current context's self (e.g. instance of MyClass), which it is absolutely not. In other words, "self" in caller's context has nothing to do with "self" implied by method's name.

After reconsidering a lot of options, my current proposal is: #then.

Reasons:

  • despite being a keyword, something.then(something) is not a conflicting Ruby syntax, and allowed by current Ruby;
  • it is short!
  • it shows intention pretty well, and reads natural, in both cases: when receives block and when returns Enumerator:
File.read(filename).then(&JSON.method(:parse))
rand(10).then.detect(&:odd?)

In many languages, .then or .and_then is useful construct, meaning the same (calculate next value from the result of the previous operation), just in a narrower context of futures/promises. I believe that even when/if Ruby will have those as a language feature, that syntax will play well:

value.then(&:computation) # => value
promise.then(&:computation) # => promise

PS: For historical reasons, here is huge list of previous proposals I've gathered for this method name.

History

#1 [ruby-core:86068] Updated by shevegen (Robert A. Heiler) about 1 month ago

I agree to your statement that yield_self is not a good, descriptive
name.

Though I have to admit that I never used yield_self so far. I can
not even say what it does, either. :)

I like yield and self. I don't like the name yield_self so I could
not use it so far. I use only a subset of ruby that makes sense
to me and I find appealing. Thankfully one can use ruby just fine
without having to use everything.

After reconsidering a lot of options, my current
proposal is: #then.

I dislike that as well. I also do not think that "then"
makes a lot of sense.

For example:

File.read(filename).then(&JSON.method(:parse))

This is regular method chaining. But it sounds like
you are using a conditional there.

"then" is already a keyword in ruby isn't it?

https://docs.ruby-lang.org/en/2.5.0/keywords_rdoc.html

I think it is confusing to have keywords and method names,
even more so when they do different things.

In many languages, .then or .and_then is useful construct, meaning
the same (calculate next value from the result of the previous
operation), just in a narrower context of futures/promises.

Realistically ruby should strive for intrinsic consistency first,
not for what features or anti-features other languages may or
may not have.

I believe that even when/if Ruby will have those as a
language feature, that syntax will play well:

value.then(&:computation) # => value
promise.then(&:computation) # => promise

I don't think it reads nicely really.

yield_self is not a good name but your proposal is also
not good, in my opinion. But it's just a personal opinion,
feel free to ignore it. At the end of the day you only have
to convince matz. :)

The name yield_self is however had indeed not a good name.
Finding good names is quite difficult. Single words are
also almost always better than combined names, even though
one is a bit limited with single words alone. There are
exceptions though. For example .each_with_index or
.each_index are good names, IMO.

#2 [ruby-core:86268] Updated by cmoel (Christopher Moeller) 27 days ago

I also agree yield_self isn't the best name but I'm also skeptical of then. Why not use map, which we already have for collections? Generally speaking, the map function isn't just about collections (though that's usually how it's used in Ruby). map is more about putting an object in a context (a block in Ruby's case), modifying the object, and returning the modified object.

Another benefit to calling the method map instead of yield_self or then is most Rubyists already understand map and how it's applied to collections, so there probably wouldn't be a large learning curve for it. It can be summarized like this: "Object#map is similar to applying map to an array containing 1 element."

Though I'm not familiar with the details, Enumerable#map and friends' implementation of map could then be an override of Object#map, yielding the block to each element in the collection, which would maintain how map currently works on collections.

Can anyone think of any issues with using map as a different/better name for yield_self?

#3 [ruby-core:86269] Updated by zverok (Victor Shepelev) 27 days ago

Why not use map

paragraphs
  .map { something }
  .reject { something }
  .yield_self { |ps| external_post_processor(ps, **options) }

#4 [ruby-core:86290] Updated by sawa (Tsuyoshi Sawada) 25 days ago

map is one of the worst names for this feature in my opinion. Generalizaing from cmoel's proposal to use map for this feature, we expect:

[1].map{|e| e * 2} #=> [1, 1]

but that is not what we currently have, nor does cmoel seem to want that. Instead, we have, and cmoel seems to want to handle as an exceptional case, the following:

[1].map{|e| e * 2} #=> [2]

That is confusing. Why do you want to make something that has worked without problems to this day to become an exception?

#5 [ruby-core:86291] Updated by sawa (Tsuyoshi Sawada) 25 days ago

I disagree with the names proposed, but I agree that the current name is too long.

Here are yet other name candidates from me:

  • deform
  • expand
  • reform

Edit. I just realized that reform has already been mentioned. Sorry.

#6 [ruby-core:86337] Updated by bughit (bug hit) 23 days ago

then does seem better but you have to keep in mind you'd be barring this name from all present and future domain specific uses in libraries and apps

What about tap! - since tap is already in use, this won't be stealing another word, and it seems to make sense, it's a "dangerous" tap that alters the flow.

#7 [ruby-core:86425] Updated by jeromedalbert (Jerome Dalbert) 18 days ago

I agree that yield_self is not a great name. But then does not "say what it does" enough in my opinion. I prefer itself: this name has problems too, but it is more descriptive at least.

#8 [ruby-core:86430] Updated by zverok (Victor Shepelev) 18 days ago

I prefer itself

itself is already taken in block-less form.

It was my initial idea too (see links in the post for names discussion), but I am happy it was not accepted: iterator-returning block-less yield_self turns out to be useful too.

#9 [ruby-core:86434] Updated by cmoel (Christopher Moeller) 17 days ago

bughit (bug hit) wrote:

What about tap! - since tap is already in use, this won't be stealing another word, and it seems to make sense, it's a "dangerous" tap that alters the flow.

I think tap! is the best option currently being discussed:

  1. There's no need to create another word for this kind of generic functionality,
  2. Rubyists already know and use tap, and
  3. Rubyists also know the ! suffix modifies the callee.

#10 [ruby-core:86458] Updated by americodls (Americo Duarte) 14 days ago

My two cents:

This method acts like a pipe operator, it passes itself through the block and returns whatever the block returns.
The block execution maybe will change the value itself, maybe not. But yield_self proposes chaining changes without mutation (tap is recommended to make mutations since tap returns the receiver itself instead of the block result).

So I think good candidates to rename or alias yield_self:

  • pipe
  • pipe_to
  • chain
  • chain_with.

#11 [ruby-core:86483] Updated by BatuhanW (Batuhan Wilhelm) 10 days ago

I think expand is cool, it is expanding method to another one, so it can be chained.

My alternative suggestion would be "pass" since it is passing itself. What do you think about it?

construct_url
  .pass(&Faraday.method(:get)).body
  .pass(&JSON.method(:parse))
  .dig('object', 'id')
  .pass { |id| id || '<undefined>' }
  .pass { |id| "server:#{id}" }

#12 [ruby-core:86569] Updated by sowieso (So Wieso) 1 day ago

I just like to stress that having an easy and short name is more important than having a name that correctly describes the behaviour. tap is also a horrible name that every ruby newbie has to look up first, still it is loved in the community. And that's especially due to the fact that it is short and feels lightweight. As for yield_self, no discussion is needed whether this method is needed, but the naming is indeed the difficult part. So I support this discussion (as annoying as it is, how many years is it now?). Let's find a better name (probably an alias)!

I would go with (in this order):

  • tip (the closest relative to tap, absolutely short, tip over suggests some movement/transfer, probably never used anywhere, tip-tap sounds like a cat walking, downside: not very descriptive at all)
  • and (like then, even shorter, focus on continuing, probably completely unused, downside: people might think of boolean comparisons, not very descriptive)
  • then (short, downside: people might think of promises, not very descriptive)
  • pass (short and somewhat descriptive, might be in use already somewhere)
  • pipe (short and more descriptive, but used in other contexts (threads, shell))

Some other ideas:

  • hand (as in hand over, short, probably never used elsewhere, descriptive? (with some imagination))
  • chain (probably in use too often)
  • use (use_here)
  • pass_me (somewhat longer, pretty clear, reads much nicer than yield_self, but I don't like the me either)
  • yield (already in use, so a no-go, without the self it's short, similar to pass, but too confusing I guess)
  • relay (probably in use too often)

I don't like tap!, I think it's misleading, as I would guess the method receiver would get mutated. map is way to confusing. Including self inside a method name is always somewhat confusing (self in which context?), so I would prefer not to do it.

So many ideas, there are definitely a lot I would be content with, we just need to decide.

#13 [ruby-core:86578] Updated by rosenfeld (Rodrigo Rosenfeld Rosas) 1 day ago

I actually liked the tip suggestion.

#14 [ruby-core:86589] Updated by matz (Yukihiro Matsumoto) about 12 hours ago

  • Assignee set to matz (Yukihiro Matsumoto)

After a long consideration, I decided to pick then as an alias to yield_self.
As zverok (Victor Shepelev) stated it describes intention, not behavior.

Because then is a reserved word, it has some restriction, but I think it is acceptable.

Matz.

Also available in: Atom PDF