Procs vs. nested methods and variable scope


#1

I’m going to be re-using this technique at quite a few places in my
code, so I want to be certain I have it right…

I started with code like this, within a method:

for playlist in playlists
with_option_values(‘find’, config) do |terms|
if (results = playlist.Search(terms, 0))
results.each {|track| tracks.push(track)}
end
end
with_option_values(‘visible-find’, config) do |terms|
if (results = playlist.Search(terms, 1))
results.each {|track| tracks.push(track)}
end
end
#Repeat several more times…
end

I of course hate repeating myself like that. I don’t want to create a
super-specialized instance or class method, though. So I tried a
nested method:

for playlist in playlists
def search_playlist(playlist, option, config, field_id, tracks)
with_option_values(option, config) do |terms|
if (results = playlist.Search(terms, field_id))
results.each {|track| tracks.push(track)}
end
end
end
search_playlist(playlist, ‘find’, config, 0, tracks)
search_playlist(playlist, ‘visible-find’, config, 1, tracks)
#Repeat several more times…
end

…I wanted to do search_playlist(option, field_id), but I had to add
parameters for the playlist, config, and tracks as well, because they
fall out of scope when the method is called. Yuck!

So finally, I went with a Proc, because I know a block/Proc stores all
variables that are in scope when it’s defined:

for playlist in playlists
search_playlist = lambda {|option, field_id|
with_option_values(option, config) do |terms|
if (results = playlist.Search(terms, field_id))
results.each {|track| tracks.push(track)}
end
end
}
search_playlist.call(‘find’, 0)
search_playlist.call(‘visible-find’, 1)
#Repeat several more times, with a lot less typing.
end

search_playlist falls out of scope when the enclosing method ends, so
I think I’m safe from playlist, tracks, etc. living forever on the
stack, right? Also, how bad is it that I’m creating the Proc inside a
(not very tight) loop?


#2

On Apr 10, 2007, at 3:45 PM, Jay McGavren wrote:

  end
  with_option_values('visible-find', config) do |terms|
     if (results = playlist.Search(terms, 1))
        results.each {|track| tracks.push(track)}
     end
  end
  #Repeat several more times...

end

What is that second argument to Search? Is it just an index of
some sort such that ‘find’ is the first search (index 0) and
‘visible-find’ is the second search (index 1) and so on? Also,
it is a bit strange to have a method capitalized.

If you change the definition of Search to return an empty array
instead of nil or false when it finds no matches and that second
argument to Search is just an index as I described then I think
you can wrap everything up as:

results = []
playlists.each do |playlist|
%w{find visible-find}.each_with_index do |option, index|
with_option_values(option, config) do |terms|
results += playlist.Search(terms, index)
end
end
end

No need to create methods or procs.

I tossed the (for…in) constructs since ‘each’ is more Ruby-like
but you could easily use for…in if that reads better to you.

And if you like using inject:

results = playlists.inject([]) do |list, playlist|
%w{find visible-find}.each_with_index do |option, index|
with_option_values(option, config) do |terms|
list += playlist.Search(terms, index)
end
end
end

If the second argument to Search is not strictly increasing with
the option names you could iterate over an array of values:

[[‘find’, 0], [‘visible-find’, 1], [‘other-opt’, 5]].each do |
option, search|
#…
end

If you need Search to return nil you could do something like:

   results += playlist.Search(terms, index) || []

Gary W.


#3

On Apr 10, 3:50 pm, Gary W. removed_email_address@domain.invalid wrote:

On Apr 10, 2007, at 3:45 PM, Jay McGavren wrote:

     if (results = playlist.Search(terms, 0))
        results.each {|track| tracks.push(track)}
     end

What is that second argument to Search? Is it just an index of
some sort such that ‘find’ is the first search (index 0) and
‘visible-find’ is the second search (index 1) and so on? Also,
it is a bit strange to have a method capitalized.

playlist is actually a WIN32OLE object (type iTunes.Application), and
that’s the form the generated methods took. The numbers/field ID
would actually be a constant with an English name (‘search all fields’
or similar in this case) if we were working in C++ and had imported a
header file.

Anyway, thanks for the pointers; I’ll look 'em over and see what I can
incorporate.