Ruby

A over a year ago now, capnbuckle complained about a Ruby script that had been inserted into large and already multilingual production environment. The task was trivial and he was quite right in throwing it out. Still, I commented that I kind of liked Ruby, and mentioned more detail later. My writing process then ground to a halt ;^)

I first ran across Ruby as “Perl with syntax that doesn’t suck”. Being a little Perl-like carries with it an implication that it has block structured syntax, which is familiar to many programmers. later I heard it called an acceptable LISP. Later still I heard a talk that said it was more or less Smalltalk, and could be implemented efficiently (at the moment it is a bit slow) using Smalltalk techniques (which Rubinius is doing, and the speaker’s company is doing more directly in Maglev.)

I was going to include the bulk of the overview I’d done for work (back when I had fellow programmers) That’s still included below, since I already had it, but I don’t really know that it answers ‘Why Ruby?’

Why Ruby?

Ruby has a relatively concise syntax that should be familiar to most programmers. (This makes the parser ugly, but I digress.) The other key feature is extreme dynamism. Objects and classes can be built and modified at runtime, including those supplied by the system. In short, if you want to do something, most of the time it won’t tell you no, where many other systems would browbeat you with with syntax errors, type errors, or just plain old errors.

These features (ease of use on the front and back end) have attracted a passionate group of artistic souls who come up with all manner of fun and useful libraries. Giles Bowkett makes a fair poster child. (Though it’s been a little while since I looked at it, the linked talk (or rather, performance) should be fairly accessible to non-programmers.) If you want to know more about Ruby, Why’s (Poignant) Guide to Ruby will serve it up – with a double helping of bizarre.

A brief and rather non-bizarre tour of notable features:

Main reference: Ruby For Rails, David A. Black, ISBN:1932394699

Everything is an object. This includes:
- numbers: 5.to_s
- strings: “5″.to_i
- symbols: :stuff.to_s.upcase.to_sym (results in a new symbol,
:STUFF)

Automatic Variables

Variables are created by assigning to them. Variables are just object
references; the object itself determines how it responds to operations.

Message sending

The nature of the object system is based on messages, where the usual
response to a message is to execute a method by the same name. The
mechanics are exposed, however:

"stuff".send("capitalize")
"stuff".respond_to?(:capitalize)
"stuff".send([:upcase, :downcase, :capitalize].rand(3))

"stuff".methods.sort
=> ["%", "*", "+", "<", "<<", (detail omitted...) "upcase", "upcase!",
"upto", "zip"]
"stuff".respond_to?('upcase') => true
"stuff".respond_to?('bounce') => false

There is also method_missing to catch the rest. I don’t care to go into detail, but this feature looms large in a number of fun and useful libraries.

Instances are live in the sense that you can add methods to individual objects.

obj = Object.new
obj.swizzle
(error message)
def obj.swizzle
  puts "Wheee!"
end
obj.swizzle
Wheee!

boring = Object.new
boring.swizzle
(error message)

Mostly Higher Order

Any method can take a ‘block’, or a procedure argument. The standard
methods use this extensively. This is similar to a lambda; Ruby has
a lambda keyword which constructs a Proc object that can be passed
around, but the implicit block handles most instances.

Blocks can be defined interchangeably with either curly braces or ‘do
… end’. By convention, braces are used for one-liners, and do/end for
larger blocks. The end keyword is also used by many language
constructs (for, while, case.)

[1, 2, 3].each do |n|
  puts n*n
end

squares = [1, 2, 3].map {|n| n*n}
sum = (1..10).inject(0) {|subtotal,n| subtotal+n}

The last example uses a range.

Writing your own methods that take code blocks is easy:

def one_two
  puts yield(1)
  puts yield(2)
end

one_two {|n| n*n}

2
4

Blocks aren’t _quite_ first class, but you can wrap classes around them:

plusTwo = Proc.new {|x| x + 2 }
plusTwo.call(3)

However, I’ve found that in most cases I actually need to reify the block. The ‘&’ allows you name the passed in block – the interface is exactly the same for callers.

def one_two(&proc)
  puts proc.call(1)
  puts proc[2]
end

Lax Syntax

puts 'hi'
puts('hi')
puts('hi');

- calls can use parentheses, but don’t have to
- ‘;’ is a statement separator, but it often isn’t required.
- Every procedure can take an block (but doesn’t have to)
- Every procedure can take a hash (but doesn’t have to)
- In procedure definitions, the value of the last statement is
implicitly returned. (but you can still use return)
- (Sometimes) implicit self.

Gotches:

Variables are Object References

a = "Hello"
b = a
a.reverse!
puts b
"olleH"

Everything is an object, but variables point to objects. Variable
assignment just assigns the pointer, so aliasing is easy to create,
often by accident. Fortunately, new objects are usually returned.
a.reverse would have created a new string and left a (and b)
alone. Most of the time only, by convention, method that include !
have side effects.

(Sometimes) Implicit self.

Inside a class or instance method, you can omit self. An exception to
this is the the ‘property=’ methods (which may come from
attr_writer) If you don’t use ‘self.property = value’, it will be interpreted
as an assignment to a local variable.

Scope does not descend

x = 1

def blarg
  puts x
end

blarg => (error)

A def or class creates a new local scope; rather than nesting, the
new local scope simply replaces the old one – the variables are no
longer visible. One method around this is global variables.

example.gsub!(‘x’, ‘$x’)

Naming Conventions

Capitalized symbols, such as class names are constants, (however the
interpreter will warn, but not prevent you from changing them.) Other
symbols might be variables, methods, or keywords. Yes, it is the _case_
of the name that identifies it – there is no const declaration.

Regular Expressions

- Match: var =~ /find.*me/, or var.match(/find.*me/)
- Replace: changed = var.gsub(/replace/, ‘with’) (or gsub! for
in-place)
- Function: var.gsub(/something/) {|s| s.capitalize}

Hashes

hash = {'one' => 1, 'two' => 2};
hash['three'] = 'five';

String Expansion

answer = 5
print 'The answer is #{answer}\n'
print "The answer is #{answer}\n"

result:

The answer is #{answer}\nThe answer is 5

Rather than simple variable interpolation, this is a complete escape:

print “2 + 2 = #{2 + 2}”

Posted Sunday, May 17th, 2009 under Essay.

2 comments