Statements in Ruby

Posted by -

In Ruby, everything is an expression. It's a big part of the design and use of the language: Rubyists aren't afraid to occasionally use the result of an if or case expression, a simple example:

  x = if foo.nil?
      then BarClass.new
      else foo
      end

  y = case foo
      when nil then BarClass.new
      else foo
      end

One of the alluring properties of Ruby to me was that it strove for purity in many areas, including the OO model (with annoying impurities such as no singletons for Fixnums notwithstanding), and this idea that everything was an expression. A class creation was an expression! A def creating a method was even an expression, even though it resembles a statement in both appearance (having a body with no do) and semantically (it is a closed scope: no closure over variables in enclosing scope). Such simplicity is tempting.

What I am slowly learning as I design my module for Laser that builds the control-flow graph for a block of Ruby code, is that Ruby actually has statements, but pretends it doesn't! Consider that there are a few things you do in Ruby code that don't have values and we only ever realistically use them as statements already. These are the control flow demons: break, next, redo, retry.

First of all, each one of those results in a jump. Even if we were to assign a value to break 3, that value can never be consumed as there is no continuation!

  self.foo(redo)

does not make much sense, does it? Try punching that into irb, and you get something you likely haven't seen before:

 >> self.foo(redo)
 SyntaxError: (irb):3: void value expression

There are many, many places in Ruby where expressions can go that a call to next truly does not belong, and whenever Ruby encounters a request for the value of one of these calls, it will throw up the void value expression Error. Keep in mind that this is a SyntaxError - it fails to parse entirely. Running Ripper.sexp('self.foo(redo)') also causes the error! The use of redo for its expression value however is an actual parsing error. Just like a statement.

It turns out Ruby 1.9.2 is going to a lot of efforts to catch some errors at the parser level, such as this code:

  class A
    def foo
      next
    end
  end

Not a syntax violation in 1.8.x, but on 1.9.2, it throws a SyntaxError for using a next in a clearly incorrect place.

Anyway, back to statements: There are times when we don't ask for a jump's value, right?

  x = 14
  while x > 10 || x == 0 || (if x == 10; p x; x = 0; next; end; x == 10)
    p x
    x -= 1
  end

There we've got a next in the condition of the loop restarting the loop at the condition again! And that code does not give a void value expression error, because the value of the if expression is never needed.

So we've got pseudo-statements that are also allowed in expressions as long as you don't try to take its value. What good is something as an expression if it has no value? Moreover, statement expressions mean can still use them in very crazy places as a result. No statementful language would allow control flow in a loop condition!

At any rate, as usual, Laser will be issuing warnings for certain places where these critters do not belong.


Enjoy this article? Then feel free to:


Comments