Feature #14594: Rethink yield_self's name - Ruby (original) (raw)

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.

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?

Why not use map

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

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?

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

Here are yet other name candidates from me:

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

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.

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.

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.

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.

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:

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}" }

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):

Some other ideas:

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.

I actually liked the tip suggestion.

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.

Applied in changeset trunk|r63525.


object.c: Add a new alias then to Kernel#yield_self; [Feature #14594]

I'm concerned that then is used in Promises, and is already in use in many promise-implementing libraries, including concurrent-ruby.

The Promise libraries could change the name of their method to not conflict, but then it's not only backwards compatibility but a mis-match with Promises in other platforms, and the original JS spec that they are based on.

The Promise libraries can keep doing this, effectively overriding the Kernel method. Somewhat confusingly then will mean something different when used on a Promise object, and Kernel#then won't be available on them. I guess since it's just an alias, this isn't a disaster, yield_self is still available, but if you're using Promises you've got to think about what kind of object you have and which alias you want, and that then means something different on Promises.

@jrochkind It is introduced that a normal object can behave like promises.
So the name conflict is intentional.
If you really wanted a non-unwrapping method for promises, use yield_self.

Matz.

matz (Yukihiro Matsumoto) wrote:

If you really wanted a non-unwrapping method for promises, use yield_self.

If I understand what you mean by "unwrapping" here, the new method still doesn't call yielded procs to make them composable: it's only an alias for yield_self, right?

Is this still a possible consideration?

Thanks for this by the way, I very much prefer the new name!

Right. I am not against the idea of function composition. But it should be discussed separately from yield_self and then. Remember I rejected #13600 but not #6284.

Matz.

a = 5
y = if a.then then 1 else 2 end

Hmm. What are you trying do with Ruby?

I suggest aliasing #yield_self with #to_be. The to_ prefix suggest returning a value while the word "be" connotes both identity (#itself) and it's new condition (&block).

Not only is it shorter, when reading it repeatedly, I also don't find it annoying. It reads just like the lyrics to the song "Welcome to My Life" by Simple Plan.

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

This looks to_be perfect, I really like this idea!

And I like pipe too, maybe even more.

I think then is going to be a bad name, because it has a special meaning in A+ Promises: https://promisesaplus.com/

This makes every object in Ruby a "thenable" object, and I think it will be bad for promise libraries.

So we will have the questionable (as confusing for promise library users) then in 2.6? There were a lot of concerns mentioned above. to_be (or other suggestions) wouldn't have these problems. I would advice to re-think this one final time, as we can't change it anymore later…

FYI: mruby 2.0 has been already released with Kernel#then. I think we've gone beyond the point of no return.

I think the word should be hit.

Example:

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