Tap { break } idiom deserves its own Kernel method?

I use this idiom from time to time:

x = expr.tap{|value| break expr-involving-value)}

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.

Obviously, this works:

id_list.map{|id| Model.find(id)}

Here’s an approach that does a single query:

unsorted = Model.where(id: id_list)
objs_map = unsorted.reduce(Hash.new) {|h,o| h[o.id] = o; h}
sorted = id_list.map{|id| objs_map[id]}

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

Any thoughts?

On Mon, Jul 22, 2013 at 7:37 PM, Andy L. [email protected]
wrote:

the order their ids appear in that list.

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?

I would choose a much less arcane solution:

objs_map = {}
Model.where(id: id_list).each {|o| objs_map[o.id] = o}
sorted = id_list.map{|id| objs_map[id]}

If id_list is smallish (< 30 or so) then index lookup might actually be
faster - or at least fast enough.

sorted = Model.where(id: id_list).sort_by {|o| id_list.index(o)}

Kind regards

robert

On Mon, Jul 22, 2013 at 10:37 AM, Andy L. [email protected]
wrote:

I use this idiom from time to time:

x = expr.tap{|value| break expr-involving-value)}

How is this any different than:

x = expr-involving-value

?

Am 22.07.2013 19:37, schrieb Andy L.:

the order their ids appear in that list.

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… :slight_smile:
But what I’m sure of is that the above tap-break idiom is too
cryptic for me.

Regards,
Marcus

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.

Andy

Robert K. wrote in post #1116270:

On Mon, Jul 22, 2013 at 7:37 PM, Andy L. [email protected]
wrote:

the order their ids appear in that list.

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?

I would choose a much less arcane solution:

objs_map = {}
Model.where(id: id_list).each {|o| objs_map[o.id] = o}
sorted = id_list.map{|id| objs_map[id]}

If id_list is smallish (< 30 or so) then index lookup might actually be
faster - or at least fast enough.

sorted = Model.where(id: id_list).sort_by {|o| id_list.index(o)}

Kind regards

robert

Am 22.07.2013 22:38, schrieb Andy L.:

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.

Regards,
Marcus

Tony A. wrote in post #1116271:

On Mon, Jul 22, 2013 at 10:37 AM, Andy L. [email protected]
wrote:

I use this idiom from time to time:

x = expr.tap{|value| break expr-involving-value)}

How is this any different than:

x = expr-involving-value

?

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.

On Mon, Jul 22, 2013 at 1:49 PM, Andy L. [email protected]
wrote:

This really mostly comes down to a way to avoid creating method-scoped
variables to hold temporary values.

Sounds like you want a let binding

I like tap itself on most occasions.

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.

unknown wrote in post #1116273:

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.

I just used the pattern again, and this time it’s a much simpler example
than the one I gave before. So maybe it will help.

The issue is I’m getting an integer parameter from a form, and I want to
store it as an integer or (if the parameter was empty) as nil.

So I’ve written this:

x = params[:distance].tap{|d| break d.empty? ? nil : d.to_i}

I’d prefer to write this:

x = params[:distance].pipe{|d| d.empty? ? nil : d.to_i}

Without these I’d need to either repeat the params[:distance] expresion,
as in the slightly damp

x = params[:distance].empty? ? nil : params[:distance].to_i

or I’d resort to a temp variable as in

dparam = params[:distance]
x = dparam.empty? ? nil : dparam.to_i

Again, nothing earth moving here. I’m just partial to the pipe approach,
and thought perhaps others would like it too.

Incidentally, I think this feature request may be in line with what
you want: Feature #6721: Object#yield_self - Ruby master - Ruby Issue Tracking System (also look at #6373,
comment 24)

Thanks - you’re right, it’s exactly what I’m proposing.

Matthew K. wrote in post #1116288:

Incidentally, I think this feature request may be in line with what
you want: Feature #6721: Object#yield_self - Ruby master - Ruby Issue Tracking System (also look at #6373,
comment 24)