Skip to content

Instantly share code, notes, and snippets.

@rogerbraun
Created October 10, 2011 21:45
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save rogerbraun/fae73df4b7063ab43c10 to your computer and use it in GitHub Desktop.
Save rogerbraun/fae73df4b7063ab43c10 to your computer and use it in GitHub Desktop.
Codebrawl #12

(STOP: If you don't want to read about the development process, you can jump directly to the part that says "SwitchBlock".)

My thoughts on passing multiple blocks

I had my coming of age as programmer when I learned Scheme (to teach it, some months later, to starry-eyed freshmen). Looking at problems from a functional perspective has colored my vision for some years now, so when I saw the challenge - "make a method that can take two blocks or more" - I immediately knew which magic technique I would have to take from the wizard book: Currying.

Currying (or if you are German: Schönfinkeln) is a method of "transforming a function that takes multiple arguments (or an n-tuple of arguments) in such a way that it can be called as a chain of functions each with a single argument" (Wikipedia). How about an example?

Let's say you have a method that takes two arguments, like addition. You want to do a + b and you know that your "a" will be five, but the second argument may depend on user input. What do you do? Well, you could save your first argument in a variable, but that's bland stuff for Java programmers. Let's spice it up and use currying.

What currying a function does is this: It takes your two-argument method and returns a method that takes just one argument. Sounds great, but where did the second argument go? Here is the brilliant part: The curried method will itself return a method! And this method also takes one argument - the "b" one we were missing - and then returns the same as the original function would have if you had not curried it.

You probably have not returned to many functions from your methods in your daily life, but as you are all Ruby cool cats you know how such a "returned function" will look like: It's a Proc! Rhymes with block, and it is essentially the same thing. So let's just write a method that block-curries other methods.

So how can we do this? Turns out you can't. Let's look at some ways of passing blocks.

Passing several blocks in their Proc variety to a function is trival. Just use them like normal arguments:

first_proc = lamdba {"Yes!!"}
second_proc = lamdba {"NO!!!!"}

def success_or_failure(condition, first_proc, second_proc)
  if condition
    first_proc.call
  else
    second_proc.call
  end
end

success_or_failure(true, first_proc, second_proc)
==> "Yes!!"

This is easy, but does not look too nice. You have to define the Procs beforehand, or in-place in the method call. The first one sucks because we do not want to use these two procs ever again, so why should we name them? The second one sucks because it looks strange and we already have a nice syntax for passing blocks of code (do/end, or { and }). I tried to get it to execute this:

blockify :success_or_failure, 2

success_or_failure(true){"Yes!!"}{"NO!!!!"}

Looks easy! If you want several blocks, just write several! But how should you access them from your method? Here is where I wanted to use currying to wrap the original method to make it accept a given number of blocks, make them into Procs and use them as the last arguments. The modified method would first take one block and return a method that again takes one. As you can probably tell, this is the part where it gets hairy.

I started programming, figuring out what instance_eval, class_eval and binding actually do, but I found a problem I could not get past: You can not use the block passing syntax on Proc objects.

If you have a Proc object, you can call it by using the #call method or its alias, #[]. Examples:

adder_proc = lambda{|x| x + 1}

adder_proc.call(1) 
==> 2

adder_proc[1]
==> 2

If you want to make this Proc take a block, you can not just use do/end as you are used to. Actually, you can not do anything except passing the block as a Proc object itself, in a regular argument slot. I don't think there is any way to pgrogram around this and I am now convinced that the syntax I hoped to implement is impossible to achieve. I would love to be wrong, though.

Less magic, but it works

After being disappointed that my favorite syntax would never work, I thought about the alternatives. I now knew that you could not just pass multiple blocks using Ruby's normal syntax. So how about wrapping several subblocks in a larger block, giving them a name while doing so:

success_or_failure(true) {
  success => {"Yes!!"}
  failure => {"NO!!!!"}
}

Does not look to bad! It is invalid syntax, though. See the "Yes" and "No" that look like blocks? Well, they aren't, they are more like Hashes with a bad syntax. How about this:

success_or_failure(true) {
  {:success => lambda {"Yes!!"},
   :failure => lambda {"NO!!!!"}}
}

Now it's a proper Hash of Procs, but it does not look that nice anymore. Anyway, you could now re-write our first method like this, and it would work:

def success_or_failure(condition)
  if condition?
    yield[:success].call
  else
    yield[:failure].call
  end
end

The call to yield will give us the block, which is just a hash that let's us select which Proc we want. This would work, but it's ugly. Using #call seems superflous, as yielding should call our block already. Of course, we actually call two blocks - the outer block with yield, the subblock with #call. We need some sweet syntactic sugar to make this go down a bit easier.

def success_or_failure(condition)
  if condition?
    yield it_to "success"
  else
    yield it_to "failure"
  end
end

success_or_failure(true){|s|
  s.success {"Yes!!"}
  s.failure {"NO!!!!"}
}

I think we have something here! This DSL (if you want to call it that) looks nice and is easy to extend to any amount of subblocks. This is what I actually implemented and documented below. I also added some methods that will make it easier for you to implement default behaviour and ensure that a subblock that should get called is actually being passed. Also, you can pass any arguments you want to"yield it_to" and they will be forwarded to your subblock.

There is no real black magic here: #it_to will build a BlockSwitcher object that remembers the name of the subblock it should execute and forwards any arguments you give. It then uses method_missing to figure out when to actually execute a subblock. Look at the code, it is only 50 lines and easy to understand. You might also want to take a look at the examples.rb file.

Some last words: You may think "hey, I have seen this before!". I thought so too! While I did not think of it while writing this module, Rails' respond_to and the form helpers use a similar technique. I don't know how they are implemented in Rails, but it is probably not too different. You may want to take a look at that, too.

Anyways, here's the real README

SwitchBlock - A way to pass multiple blocks to a method

Switchblock is a module mixin that adds a simple way to pass multiple blocks to methods to your classes. It does this by creating an object that acts as a switch block.

Usage

First, you must include SwitchBlock in your class (or your program). You can now write methods that take multiple blocks using the "it_to" method like this:

def did_we_succeed?(success)
  if success
    yield it_to "success"
  else
    yield it_to "failure"
  end
end

did_we_succeed?(true) do |s|
  s.success {"Yes!!"}
  s.failure {"Aw... Bummer..."}
end

=> "Yes!!"

The call to it_to will give you BlockSwitcher, which will take care of the block you give to the method and execute just the subblock(s) you want.

Features

As seen in the usage example, you can pass an unlimited number of subblocks to your BlockSwitcher. These will be executed if the method you call on the BlockSwitcher matches whatever name you specified in the "yield it_to" call.

Switches

Switches are really simple, just make up a name and use it to call one your subblocks by yielding to it in your "yield it_to" call. The first argument to "it_to" is the name of the block you want to call. You can use anything that has a #to_sym method.

def yield_name(name)
  yield it_to name
end

yield_name("Larry") do |s|
  s.larry {"Hi, Larry!}
end

=> "Hi, Larry!"

Instead of blocks, you can also use a proc as the argument to a switch.

larry_greeter = lambda {"Hi, Larry"}

yield_name("Larry") do |s|
  s.larry larry_greeter
end

=> "Hi, Larry!"

Any additional arguments you yield after the switch name will be passed on to your subblock.

def even_or_odd(x)
  if x.even?
    yield it_to "even", x
  else
    yield it_to "odd", x
  end
end

even_or_odd(5) do |s|
  s.even {|x| "#{x} is an even number" }
  s.odd {|x| "#{x} is an odd number" }
end

=> "5 is an odd number"

The return value of your "yield it_to" call will be the return value of the last executed subblock. If nothing at all is executed, the whole block will return nil. It will raise an error if you use "ensure" (see below).

Special Switches

You can use any name for your switch, but "else", "always" and "ensure" have a special meaning and are treated differently.

always

You can use "always" as often as you wish, just as normal switches, and it will always execute no matter what.

else

You should use "else" only as the last switch of your switchblock. The block you give to it will only execute if no previous block executed before. You can use this to implement default behaviour.

ensure

You should use "ensure" only as as the last switch of your switchblock. It will ignore all passed blocks and arguments. It makes sure that at least one of your switches was called during execution and will raise a NothingExecutedError otherwise.

You can use this to protect against typos. For example, you mistype and write "yield it_to 'succes'" and you have no idea why the block passed to "s.success" in your switchblock is not executed. Using ensure will give you a nice error message in this case, telling you what should have been executed and what was actually present.

Needless to say, you should probably not use "ensure" together with "always" or "else". You can though, if you are really clever (or just feel like it).

Notes

It uses BlankSlate (https://github.com/masover/blankslate) instead of BasicObject because using BasicObject as a superclass breaks error handling. Also, BlankSlate works with 1.8

The method that generates the BlockSwitcher object is called "it_to" instead of to mainly because Sinatra already has a "to" helper method.

#encoding: utf-8
require "./multiblock.rb"
include SwitchBlock
def even_or_odd(x)
if x.even?
yield it_to "even", x
else
yield it_to "odd", x + 1
end
end
res = even_or_odd(4) do |s|
s.even {|x| puts "I got #{x}, which was even already"; x}
s.odd {|x| puts "I got #{x}, which was odd"; x}
s.always{|x| puts "nice, isn't it?"; x + 2}
end
def fizzbuzz(x)
yield it_to "Fizz" if x % 3 == 0
yield it_to "Buzz" if x % 5 == 0
yield it_to "num", x if x % 3 != 0 and x % 5 != 0
print "\n"
end
(1..100).each do |x|
fizzbuzz(x) do |s|
s.fizz {print "Fizz"}
s.buzz {print "Buzz"}
s.num {|x| print x}
end
end
def this_works
puts "This should be success"
yield it_to "success"
end
def this_works_not
puts "This just won't work"
yield it_to "failure"
end
def this_does_something_we_did_not_expect
puts "This has a result that is not handled"
yield it_to "wtf"
end
block = lambda do |s|
s.success { puts "-- We did it! ヤッタ!!"}
s.failure { puts "-- We failed! 桜散る。。。"}
s.else { puts "-- What is this I don't even"}
end
this_works(&block)
this_works_not(&block)
this_does_something_we_did_not_expect(&block)
success_block = lambda {puts "Using a lambda or proc in your block also works!"}
this_works do |s|
s.success success_block
end
# Using the ensure-switch
def this_will_break
yield it_to "succes" # <-- typo
end
begin
this_will_break do |s|
s.success success_block
s.ensure
end
rescue => e
puts e
end
#encoding: utf-8
require "pry"
require "blankslate"
module SwitchBlock
class NothingExecutedError < Exception; end
class BlockSwitcher < BlankSlate
def initialize(x, args)
@x = x
@args = args
@ret = nil
@called = false
@callees = []
end
def ensure
raise NothingExecutedError, "Nothing got executed! Expected one of #{@callees.inspect}, but only got #{@x}" if not @called
end
def method_missing(method,*args, &block)
if (method.downcase == @x.to_sym.downcase or method == :always) or (@called == false and method == :else) then
@called = true
if args.size > 0 then
@ret = args.first.call(*@args)
else
@ret = block.call(*@args)
end
end
# For error reporting
@callees << method
@callees.uniq!
# Always return something meaningful
@ret
end
end
private
def it_to(x, *args)
BlockSwitcher.new(x, args)
end
end
require "./multiblock.rb"
require "pry"
describe SwitchBlock do
include SwitchBlock
def switch_helper(name, *args)
yield it_to name, *args
end
let(:yes_no_block) {
lambda do |s|
s.success {"yes"}
s.failure {"no"}
end
}
let(:ensured_block) {
lambda do |s|
s.success {"yes"}
s.failure {"no"}
s.ensure
end
}
let(:block_with_default) {
lambda do |s|
s.success {"yes"}
s.failure {"no"}
s.always {"ok"}
end
}
let(:block_with_else) {
lambda do |s|
s.success {"yes"}
s.failure {"no"}
s.else {"maybe"}
end
}
it "should be able to switch between codeblocks" do
res = switch_helper("success", &yes_no_block)
res.should == "yes"
res = switch_helper("failure", &yes_no_block)
res.should == "no"
end
it "should return nil if no codeblock is run" do
res = switch_helper("N/A", &yes_no_block)
res.should be_nil
end
it "should also take procs as subblocks" do
procblock = lambda{"procblock"}
res = switch_helper("procblock") do |s|
s.procblock procblock
end
res.should == "procblock"
end
it "should execute 'always' switches" do
res = switch_helper("N/A", &block_with_default)
res.should == "ok"
end
it "should return the last evaluated subblock" do
res = switch_helper("success", &block_with_default)
res.should == "ok"
end
it "should raise an error if ensure is used and no code has run" do
lambda{ switch_helper("suxess", &ensured_block)}.should raise_error
end
it "should not raise an error if ensure is used and code has run" do
lambda{ switch_helper("success", &ensured_block)}.should_not raise_error
end
it "should execute else blocks if nothing else has run" do
res = switch_helper("N/A", &block_with_else)
res.should == "maybe"
end
it "should not execute else blocks if something else has run" do
res = switch_helper("failure", &block_with_else)
res.should == "no"
end
end
@elight
Copy link

elight commented Oct 17, 2011

I found your semantics for handling multiple blocks elegant. Also highly approve of the inclusion of tests/specs!

@elight
Copy link

elight commented Oct 17, 2011

Also enjoyed reading your thought processes as you evolved toward a solution. But that didn't weigh into my scoring. ;-)

@JEG2
Copy link

JEG2 commented Oct 17, 2011

This is similar to my own solution and it works well. I like your inclusion of special switches.

@elight
Copy link

elight commented Oct 17, 2011 via email

@jeremyevans
Copy link

I like this, it's relatively simple and provides the necessary features. I think the only problem is that it's creating a new BlockSwitcher object in every yield.

@JEG2
Copy link

JEG2 commented Oct 17, 2011

Evan: No, but you could rewrite it as:

call( Class.new do
  # ...
end )

@elight
Copy link

elight commented Oct 17, 2011

Oh hrm. Comment appeared on wrong gist. Sorry about that.

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