Skip to content

Instantly share code, notes, and snippets.

@jeremyevans
Created October 10, 2011 20:43
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 jeremyevans/4f3b53bfeb375a3e6d62 to your computer and use it in GitHub Desktop.
Save jeremyevans/4f3b53bfeb375a3e6d62 to your computer and use it in GitHub Desktop.
Entry for "Methods taking multiple blocks" CodeBrawl
My solution to "Methods taking multiple blocks" is called MultiProc, and it
allow you to register multiple procs by name using a method chaining API.
You pass the object as the block argument to the method (using &, as
MultiProc implements #to_proc). The method receiving the block can use
one of two APIs for call the blocks. It can either call methods on the
block object by name, or it can use yield or block.call with the first
argument being a symbol for the related method.
Examples:
Here's a method that you can pass true or false to. Passing true calls
the :true block, passing false calls the :false block. This example
shows the use of yield and block.call with a symbol as the first argument
to specify which block to call. It also shows how you can pass blocks
to the block arguments.
def a(flag, &block)
if flag
block.call(:true, 1, "two"){|b| [b, :foo]}
else
yield :false
end
end
Here's how you would use MultiProc to register the true and false blocks:
mp = MultiProc.new.
true{|arg1, arg2, &b| [true, arg1, arg2, b.call(:three)]}.
false{false}
This uses a fairly simple approach with method_missing being used to
register blocks by name, and having it return self so that you can use
it in a method chaining fashion.
Calling the a methods with the MultiProc is as simple as passing it as a
block argument using &:
a(true, &mp)
# => [true, 1, "two", [:three, :foo]]
a(false, &mp)
# => false
The second example here calls three separate blocks: before, run, and
after. It uses the 2nd type of calling API, where you call blocks as
methods by name on the block argument.
def b(&block)
block.before(:b)
block.run(:r)
block.after(:a)
nil
end
Here's an example of calling the method:
b(&MultiProc.new.
run{|x| p [:run, x]}.
before{|x| p [:before, x]}.
after{|x| p [:after, x]})
# => nil
# Output:
# [:before, :b]
# [:run, :r]
# [:after, :a]
class MultiProc < BasicObject
def initialize
@procs = {}
end
def call(name, *args, &block)
unless b = @procs[name]
raise ArgumentError, "proc for #{name.inspect} not registered"
end
b.call(*args, &block)
end
def method_missing(name, &block)
@procs[name] = block
self
end
def to_proc
p = Proc.new(self){|*args, &block| p.call(*args, &block)}
end
class Proc < ::Proc
def initialize(mp)
@multiproc = mp
super()
end
def call(*args, &block)
@multiproc.call(*args, &block)
end
alias method_missing call
end
end
@JEG2
Copy link

JEG2 commented Oct 17, 2011

This solution feels the most like Smalltalk to me and that language was the inspiration for this problem.

@jeremyevans
Copy link
Author

Interesting considering I've never programmed in Smalltalk. :)

@Keoven
Copy link

Keoven commented Oct 17, 2011

Wow, so that's what smalltalk looks like. Really like the idea.. :)

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