Is There a More Concise Way to Call an Outside Method on a Map in Ruby

Is there a more concise way to call an outside method on a map in Ruby?

Yes, possible this way, but not good for performance (see post: Is the &method(:method_name) idiom bad for perfomance in Ruby?):

targets = ['./foo', './bar', './free']
targets.map(&Dir.method(:exists?))
# => [false, false, false] #all are false,as I don't have such directories.

Is the &method(:method_name) idiom bad for performance in Ruby?

Yes, it appears to be bad for performance.

def time
start = Time.now
yield
"%.6f" % (Time.now - start)
end

def do_nothing(arg)
end

RUBY_VERSION # => "1.9.2"

# small
ary = *1..10
time { ary.each(&method(:do_nothing)) } # => "0.000019"
time { ary.each { |arg| do_nothing arg } } # => "0.000003"

# large
ary = *1..10_000
time { ary.each(&method(:do_nothing)) } # => "0.002787"
time { ary.each { |arg| do_nothing arg } } # => "0.001810"

# huge
ary = *1..10_000_000
time { ary.each(&method(:do_nothing)) } # => "37.901283"
time { ary.each { |arg| do_nothing arg } } # => "1.754063"

It looks like this is addressed in JRuby:

$ rvm use jruby
Using /Users/joshuajcheek/.rvm/gems/jruby-1.6.3

$ xmpfilter f.rb
def time
start = Time.now
yield
"%.6f" % (Time.now - start)
end

def do_nothing(arg)
end

RUBY_VERSION # => "1.8.7"

# small
ary = *1..10
time { ary.each(&method(:do_nothing)) } # => "0.009000"
time { ary.each { |arg| do_nothing arg } } # => "0.001000"

# large
ary = *1..10_000
time { ary.each(&method(:do_nothing)) } # => "0.043000"
time { ary.each { |arg| do_nothing arg } } # => "0.055000"

# huge
ary = *1..10_000_000
time { ary.each(&method(:do_nothing)) } # => "0.427000"
time { ary.each { |arg| do_nothing arg } } # => "0.634000"

How to turn a Ruby method into a block?

You can use Object#method(method_name):

filenames.map(&File.method(:size))

Shorthand for functions to map

use this:

nums.each(&method(:puts))

but actually it's not much shorter :)

How do I break out of a map/collect and return whatever has been collected up to that point?

Instead of using map directly, build up your own collection and then use the fact that break returns a value to abort early:

result = 
[0, 1, 2, 1, 0].each_with_object([]) { |val, accumulator|
if val < 2
accumulator << val
else
break accumulator
end
}
result # => [0, 1]

If we did just break (instead of break accumulator) then nil would be implicitly returned and result would just be set to nil.

This solution has the advantage of only allocating a single accumulator Array and only having to loop once.

Refactoring if elsif else or in Ruby block

Maybe something like this would help?

I've updated my answer for splitting array on two groups.

def main_method
methods = [:method1, :method2, :method3]
non_passed_elems, passed_elems = some_array.partition do |elem|
methods.none? do |method|
send(method, elem)
end
end
passed_elems.each{ |t| method_for_passed_elems(t) }
non_passed_elems.each{ |t| method_for_non_passed_elems(t) }
end

Delegate methods to the result of a method

I figured it out and it's incredibly simple. Just delegate to the name of the method. Here's a working example:

class MyClass
extend Forwardable
delegate %w([] []=) => :build_hash

def build_hash
return {'a'=>1}
end
end

Calling variable name of instance from within class

Unconventional method (solves the question you posed)

Okay. For the sake of posterity I'll put the code here on how to accomplish what you asked. Remember this is NOT the way the language was meant to be used. But if you get into meta-programming this will be very useful knowledge.

class Playlist

def display
puts "Your playlist name is #{name}"
end

private
def name
scope = ObjectSpace.each_object(Binding).to_a[-1]
scope.
local_variables.
select {|i| eval(i.to_s, scope) == self}.
map(&:to_s).delete_if {|i| i== "_"}.first
end

end

alternative = Playlist.new
# => #<Playlist:0x00000002caad08>
alternative.display
# Your playlist name is alternative

Details (how it works)

Alright let me explain the parts. ObjectSpace is where all objects get stored. You can see how many Objects exist by calling ObjectSpace.count_objects. The most useful feature, in my opinion, is the each_object method. With this method you can iterate over however many of any particular object which have been created. So for playlist you can call ObjectSpace.each_object(Playlist) and you get an Enumerable object. We can simply turn that into a list by appending .to_a on the end. But at this point you're getting the instances of Playlist in an Array like this: [#<Playlist:0x0000000926e540>, #<Playlist:0x000000092f4410>, #<Playlist:0x000000092f7d90>]. This is functional if you wanted to access them individually and perform some action. But this is not what you're trying to do since we don't have the instantiated variable name these instances are assigned to.

What we really want to call is the local_variables method and we want to call that in the main scope (not from within your classes scope). If you call local_variables from within your display method you get back an empty Array []. But if you call it in the main console after you've created an instance you would get back something like this [:alternative, :_]. Now we're talking! Now there's the issue of getting the scope from outside the class to be used within it. This was tricky to track down. Normally you could just pass in binding as a parameter, or even use TOPLEVEL_BINDING. But something I noticed showed me that these each create an instance of Binding that won't get updated any more. That means once you call TOPLEVEL_BINDING anything else you define, like another playlist, won't be updated and in your list of TOPLEVEL_BINDING.local_variables. This was a sad thing for me to find. But I discovered a way to solve this.

By calling ObjectSpace.each_object(Binding).to_a we now have a list of every binding instance. So we just need to know how to get the latest one that's up to date. After experimenting I found the last one will always be up to date. So we index by [-1]. Now we can call .local_variables on it and we will always get the latest collection of instance variables within the global scope. This is great! Now we just need to match the instance variable to the current Playlist that we're in. So we select from the global local_variables any that match the current instance. We need to call eval to get the instance, and with eval we need to tell it what scope to run in so we use select {|i| eval(i.to_s, scope) == self}. From there we take the symbols and map them to strings with .map(&:to_s) and lastly we have an extra item in our list we don't need. The underscore symbol is kind of a Ruby trick to get the last thing that was processed. So we'll need to remove it since it evaluated to the same id as our current variable instance did. So we do .delete_if {|i| i== "_"}. And lastly it's a list of one item, the thing we want, so we pick it out with .first

NOTE: This scope selecting method doesn't work in Rails. There are many bindings instantiated. The last one and the largest one with local_variables aren't the up to date ones.

This went through many unconventional means to accomplish the task you asked about. Now it may be you didn't know the standard way that something like naming a playlist class is done, and that's okay. No one knew at first, it is a learned trait.

Convential way to name a playlist

This is the preferred method for naming a playlist class.

class Playlist

def initialize(name)
@name = name
end

def display
puts "Your playlist name is #{@name}"
end

end

list = Playlist.new("Alternative")
list.display
# => "Your playlist name is Alternative"

This is rather straight forward. It's best to work with the way a language was designed to be used.

If I were you I would make list an Array of Playlist items and use it like this.

list = []
list << Playlist.new("Alternative")
list << Playlist.new("Rock")
list
# => [#<Playlist:0x000000028a4f60 @name="Alternative">, #<Playlist:0x000000028e4868 @name="Rock">]
list[0].display
# Your playlist name is Alternative
list[1].display
# Your playlist name is Rock

And now you have a list of playlists! Sweet!

When you get into meta-programming you may use a lot of features from the unconventional method here. meta-programming is where code writes code. It's fun!

Blocks & Procs in Ruby

There are a lot of things that are good about blocks. The elevator pitch: Blocks let us pass around actions the same way we normally pass around data.

The most obvious level is that they let you abstract things out into functions that wouldn't be possible otherwise. For example, let's look at a common case where you have a list of things and you want to filter it to only include items that match some criterion:

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
int evenNumbers[50] = {0};
int copyIndex = 0;
for (int i = 0; i < 50; i++) {
if (list[i] % 2 == 0) {
evenNumbers[copyIndex++] = list[i];
}
}

Here's how you write that in Ruby:

list = 1..50
listCopy = list.select {|n| n.even?}

All the common busywork is moved out of your code and into a method with a meaningful name. We don't care about copying the array and going through indexes and all that — we just want a filtered list. And that's what select gives us. The block allows us to pass our custom logic into this standard method.

But iterators aren't the only place where this "hole in the middle pattern" is useful. For example, if you pass a block to File.open, it will open the file, execute the block with the file and then close the file for you.

Another thing that blocks give us is a really powerful form of callbacks. For example, without blocks, we might have to do something like this (based on how dialogs actually work in Objective-C Cocoa):

class Controller
def delete_button_clicked(item)
item.add_red_highlight
context = {:item => item}
dialog = Dialog.new("Are you sure you want to delete #{item}?")
dialog.ok_callback = :delete_OK
dialog.ok_receiver = self
dialog.cancel_callback = :cancel_delete
dialog.cancel_receiver = self
dialog.context = context
dialog.ask_for_confirmation
end

def delete_OK(sender)
delete(sender.context[:item])
sender.dismiss
end

def cancel_delete(sender)
sender.context[:item].remove_red_highlight
sender.dismiss
end
end

Yowza. With blocks, we could do this instead (based on a common pattern used in many Ruby libraries):

class Controller
def delete_button_clicked(item)
item.add_red_highlight
Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response|
response.ok { delete item }
response.cancel { item.remove_red_highlight }
end
end
end

That's actually two levels of blocks — the do...end block and the two {}-style blocks. But it reads pretty naturally, doesn't it? This works because a block captures the context it's created in, so we don't need to pass around self and item.

As for Procs, they're just an object wrapper for blocks. Not very much to them.



Related Topics



Leave a reply



Submit