rubycaseabstract-syntax-treeequivalenceruby-parser

Equivalence between ifs and forms of case statements in Ruby


Using the ruby_parser and Ruby2Ruby gems, I'm writing code that keeps track of what conditions have been evaluated and what their results and parameters were. In order to keep this as simple as possible, I sometimes rewrite the AST a bit. Of course, I can only do that if I'm sure the result functions exactly the same as the original.

Am I correct in asserting that the following three Ruby snippets are equivalent in function, assuming the triple dots are replaced by a valid Ruby expression? Am I overlooking any edge cases?

case var
  when foo 
    something
  when ... 
    another_thing
  else 
    something_else
end

if foo === var 
  something
elsif ... === var
  another_thing
else  
  something_else
end

case
  when foo === var 
    something
  when ... === var 
    another_thing
  else 
    something_else
end

Solution

  • Those three snippets are equivalent iff var is idempotent, i.e. evaluating var multiple times has the same side-effects as evaluating it once.

    So, if var really is a variable, you are safe, but remember that it can be an arbitrary expression, including a message send to a side-effecting method (like puts).

    E.g.

    case puts('Hello')
    when 1
    when 2
    

    is not the same as

    if 1 === puts('Hello')
    elsif 2 === puts('Hello')
    

    because in the latter case, "Hello" will be printed twice.

    A better translation might be:

    __randomly_generated_guaranteed_unique_local_variable_jhggfq45g345 = var
    
    if foo === __randomly_generated_guaranteed_unique_local_variable_jhggfq45g345 
      something
    elsif ... === __randomly_generated_guaranteed_unique_local_variable_jhggfq45g345
      another_thing
    else  
      something_else
    end
    
    case
      when foo === __randomly_generated_guaranteed_unique_local_variable_jhggfq45g345
        something
      when ... === __randomly_generated_guaranteed_unique_local_variable_jhggfq45g345
        another_thing
      else 
        something_else
    end