From: Robert Klemme Date: 2013-07-23T05:14:01+09:00 Subject: Re: tap { break } idiom deserves its own Kernel method? --e89a8f5038b21e433d04e21f5153 Content-Type: text/plain; charset=ISO-8859-1 On Mon, Jul 22, 2013 at 7:37 PM, Andy Lowry wrote: > 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? > 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 -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/ --e89a8f5038b21e433d04e21f5153 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable



On Mon, Jul 22, 2013 at 7:37 PM, Andy Lowry <<= a href=3D"mailto:lists@ruby-forum.com" target=3D"_blank">lists@ruby-forum.c= om> wrote:
I use this idiom from time to time:

=A0 x =3D 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:

=A0 =A0id_list.map{|id| Model.find(id)}

Here's an approach that does a single query:

=A0 =A0unsorted =3D Model.where(id: id_list)
=A0 =A0objs_map =3D unsorted.reduce(Hash.new) {|h,o| h[o.id] =3D o; h}
=A0 =A0sorted =3D 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&qu= ot;:

=A0 =A0sorted =3D Model.where(id: id_list).tap do |unsorted|
=A0 =A0 =A0break unsorted.reduce(Hash.new) {|h,o| h[o.id] =3D o; h}
=A0 =A0end.tap do |objs_hash|
=A0 =A0 =A0break id_list.map{|id| objs_hash[id]}
=A0 =A0end

I find enough uses for this idiom that I'm thinking it'd be worth g= iving
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.

=A0 Kernel.module_eval do
=A0 =A0 def pipe
=A0 =A0 =A0 yield self
=A0 =A0 end
=A0 end

Then the example above turns into:

=A0 sorted =3D Model.where(id: id_list).pipe do |unsorted|
=A0 =A0 unsorted.reduce(Hash.new) {|h,o| h[o.id] =3D o; h}
=A0 end.pipe do |objs_map|
=A0 =A0 id_list.map{|id| objs_hash[id]}
=A0 end

Any thoughts?

=A0I would choose a much = less arcane solution:

objs_map =3D {}
Mo= del.where(id: id_list).each {|o| objs_map[o.id] =3D o}
sorted =3D id_list.map{|id| objs_map[id]}

If id_list is smal= lish (< 30 or so) then index lookup might actually be faster - or at lea= st fast enough.

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

Kind regards

robert


--
remember.gu= y do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
--e89a8f5038b21e433d04e21f5153--