-
-
Save jeremyevans/4f3b53bfeb375a3e6d62 to your computer and use it in GitHub Desktop.
Entry for "Methods taking multiple blocks" CodeBrawl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Interesting considering I've never programmed in Smalltalk. :)
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
This solution feels the most like Smalltalk to me and that language was the inspiration for this problem.