4

I'm porting a JavaScript library to Ruby, and have come across the following insanity (heavily abbreviated):

function foo(){
  if (foo) ...
  loop:
    while(go()){
      if (...) break;
      switch(...){
        case a:
          break loop;  
        case b:
        case c:
          if (...) break loop;
          ...
          break;
        case d:
          if (...) break loop;
          // fall through
        case e:
          if (...) break loop;
          ...
          break;    
        case f:
          if (...) break loop;
          object_init:
            do{
              switch(...){
                case a:
                  ...
                  break;
                case b:
                  ...
                  break object_init;        
              }
            } while(...);              
            ...
            break;
      }
    }
}

(You can view the full horror on lines 701-1006.)

How would you rewrite this in Ruby? Specifically:

  • Handling the intermixed break and break loop, and
  • Handling the occasional "fall throughs" that occur in the switch

Presumably a good general strategy for these will get me through other situations, like the nested object_init breaking that also occurs.

Edit: How silly of me; a JavaScript "fall through" like this:

switch(xxx){
  case a:
    aaa;
  case b:
    bbb;
  break;
}

can easily be rewritten in Ruby as:

case xxx
  when a, b
    if a===xxx
      aaa
    end
    bbb
end
Phrogz
  • 296,393
  • 112
  • 651
  • 745

1 Answers1

4

There are multiple techniques that will work for this.

  1. I'm sure this has already occurred to you, but for the record, you could extract methods from the nightmare function until its structure looks more reasonable.

  2. You could define the outer loops with lambda and then immediately call them on the next line. This will allow you to use the return statement as a multi-level break and the closure that is created will allow you to still access the outer scope variables.

  3. You could raise an exception and rescue it.

  4. (Added by Phrogz) As suggested in the answer linked by @jleedev, you can use throw/catch, e.g.

    catch(:loop) do
      case ...
        when a
          throw :loop
        when b, c
          throw :loop if ...
        ...
      end
    end
    
Phrogz
  • 296,393
  • 112
  • 651
  • 745
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • There are some tricky things to look out for with #2. If any of the loops *within* the **lambda** block are implemented with **Proc** objects, a **return** inside one of *those* will return from your entire surrounding method, not just the **lambda**. So, for example, that means you cannot iterate inside the **lamba** with something like `5.times do ... end` unless that one loop doesn't use **return**. – DigitalRoss Feb 14 '11 at 01:41
  • Failing any further answers, I'll accept this. I suggest that this would be even better if you include throw/catch in your list of techniques. – Phrogz Feb 19 '11 at 02:18
  • I've added the throw/catch answer. Feel free to re-edit your answer to include it as you would like to word it and throw my name out of your answer. – Phrogz Feb 19 '11 at 02:21