It works well, but reading it requires the reader to understand a
relatively obscure part of ruby, namely the behavior of break during a
yield.
Here’s my most recent case where the idiom useful: I’ve got a list of
model object ids, and I want produce an array of the model objects in
the order their ids appear in that list.
Here’s the same thing using tap/break, to make it more obvious that the
only thing I’m really interested in is that final value “sorted”:
sorted = Model.where(id: id_list).tap do |unsorted|
break unsorted.reduce(Hash.new) {|h,o| h[o.id] = o; h}
end.tap do |objs_hash|
break id_list.map{|id| objs_hash[id]}
end
I find enough uses for this idiom that I’m thinking it’d be worth giving
it a name and its own Kernel method, rather than forcing the use of a
relatively narrowly understood language feature (behavior of break
during yield). “pipe” or “transform” may be a good name for it.
Definition would be almost identical to that of tap.
Kernel.module_eval do
def pipe
yield self
end
end
Then the example above turns into:
sorted = Model.where(id: id_list).pipe do |unsorted|
unsorted.reduce(Hash.new) {|h,o| h[o.id] = o; h}
end.pipe do |objs_map|
id_list.map{|id| objs_hash[id]}
end
it a name and its own Kernel method, rather than forcing the use of a
Then the example above turns into:
sorted = Model.where(id: id_list).pipe do |unsorted|
unsorted.reduce(Hash.new) {|h,o| h[o.id] = o; h}
end.pipe do |objs_map|
id_list.map{|id| objs_hash[id]}
end
Any thoughts?
To me this looks like defining a method for doing method chaining…?!
IMO, `tap’ is overused or often even misused anyway, but probably
I only think that because I do not get its many advantages…
But what I’m sure of is that the above tap-break idiom is too
cryptic for me.
This is my first code sample but with one temporary variable removed.
Getting rid of the remaining temp variable is much more difficult. You
could inline the “Model.where…” expression but then you’d end up
executing it repeatedly in the id_list loop. The reason I use the idiom
is to avoid cluttering my namespace with names for things that are of
ephemeral interest, i.e. only useful as a step in achieving some needed
value. In my opinion, those values are less prominent in the “pipe”
version of my code than in either my first code sample or in your code,
and that seems valuable to me.
The only thing that seems arcane to me in this thread is using break
inside tap; as I pointed out, it’s something a lot of ruby coders won’t
understand. Code blocks with well-defined behaviors are obviously well
understood by any ruby developer who’s not a total novice. I believe
that “pipe” would take a useful idiom that is seen occasionally (not
just in my code) and is arcane because of the tap/break combo, and
provide a non-arcane alternative.
The reason I use the idiom
is to avoid cluttering my namespace with names for things that are of
ephemeral interest, i.e. only useful as a step in achieving some needed
value.
I would always value easily readable code higher than an
“uncluttered” namespace, but tastes are different.
It’s the tap that makes the variable “value” available for use in
expr-involving-value.
This really mostly comes down to a way to avoid creating method-scoped
variables to hold temporary values. The block parameters end up taking
their place, which in my opinion makes them less prominent in the code,
as they should be.
Obviously there’s nothing earth-shattering going on here. Just something
I believe would be a nice and very small addition.
The point of tap for me is being declarative, you are telling the reader
the intention upfront. The alternative without tap for him is to infer
from the listing.
At first sight I don’t quite see #pipe though, it basically moves a
variable to a block parameter. Not sure it is worth a new idiom.
But what I’m sure of is that the above tap-break idiom is too
cryptic for me.
Regards,
Marcus
I agree! But I use it, because I like the pattern when it fits, and it’s
what’s available. I’d prefer to use the same pattern with something less
cryptic like my pipe proposal.