Skip to content

Instantly share code, notes, and snippets.

@koraktor
Created October 11, 2011 07:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save koraktor/557b7ebd3331710d3fd8 to your computer and use it in GitHub Desktop.
Save koraktor/557b7ebd3331710d3fd8 to your computer and use it in GitHub Desktop.
CodeBrawl contest "Methods taking multiple blocks"

CodeBrawl contest: Methods taking multiple blocks

This entry to the aforementioned CodeBrawl contest adds some extensions to facilitate the handling of multiple blocks (aka. Procs or lambdas) as method arguments. It is as simple as possible while maintaining a good usability.

Usage

def method_with_multiple_blocks(*blocks)
  blocks.extend ProcArray   # Add basic block handling to the `blocks` array
  blocks.min 1              # Require at least 2 given blocks
  blocks.max 2              # Allow at most 3 given blocks
  blocks.required :foo      # Require a block with the given name
  blocks.arities nil, 1     # Check that block #2 accepts an argument

  blocks[0]        # call a block by position (may be not that useful)
  blocks.bar 'baz' # call a named block (with argument)
end

bar = lambda { |baz| puts baz }   #  Create a block and ...
bar.name = :bar                   # ... explicitly set its name
foo = lambda { puts 'bar' }.foo   # The short syntax allows to set names that don't clash with existing methods of `Proc` (e.g. `#call`)

method_with_multiple_blocks foo, bar

# bar
# baz

Caveats

Right now, there's no control over multiple Procs with the same names. This could be easily added, but currently the first Proc in a ProcArray will "win".

def do_something(*blocks)
blocks.extend ProcArray
blocks.required :success, :failure, :error
blocks.arities nil, nil, 1
status = rand(3)
begin
case status
when 0 then blocks.success
when 1 then blocks.failure
when 2 then raise 'error'
end
rescue
blocks.error $!
end
end
s = lambda { puts 'success' }.success
f = lambda { puts 'failure' }.failure
e = lambda { |error| puts "error: #{error}" }.error
do_something s,f,e
module ProcArray
def [](index)
obj = super
obj.is_a?(Proc) ? obj.call : obj
end
def arities(*arg_counts)
each_with_index do |obj, idx|
next unless obj.is_a? Proc
arg_count = arg_counts[idx]
unless arg_count.nil? || arg_count == obj.arity
error = "Block ##{idx+1} must "
if arg_count > 0
error << "accept exactly #{arg_counts} arguments."
elsif arg_count == 0
error << 'not accept any arguments.'
elsif arg_count < 0
error << "accept #{arg_count.abs - 1} or more arguments."
end
raise error
end
end
end
def find_proc(name)
find { |obj| obj.is_a?(Proc) && obj.name == name }
end
def max(proc_count)
raise 'You supplied too many blocks.' if size > proc_count
end
def method_missing(name, *args, &block)
proc = find_proc name
return proc.call *args, &block unless proc.nil?
super
end
def min(proc_count)
raise 'You supplied too few blocks.' if size < proc_count
end
def required(*names)
names.each do |name|
if find_proc(name).nil?
raise "You didn't supply a block called '#{name}'."
end
end
end
end
module NamedProc
attr_accessor :name
def method_missing(name)
@name = name
self
end
end
class Proc
include NamedProc
end
@JEG2
Copy link

JEG2 commented Oct 17, 2011

This is an interesting approach, but I prefer to work by name at both ends of this problem.

@jeremyevans
Copy link

Proc#method_missing returning self in all cases is probably not a good thing. This also requires your method to accept an abitrary number of arguments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment