home

 

Ruby Intro

As you might guess from the name, Ruby was inspired by Perl. It's a "scripting" language, but otherwise not that similar. It does keep Perl's love of funny symbols and multiple odd ways of doing the same thing. It claims to be more Object Oriented, but I don't see it. The most obnoxious feature is marking blocks with BEGIN-END tags. All-in-all, Ruby is clunky.

Why use it? I guess for web-app Ruby on Rails, and legacy applications.

Whitespace, comments, declaring...

Ruby uses modern scripting sloptastic whitespace rules. Semi-colons go between statements on the same line, but aren't always needed. Newlines end statements, unless they shouldn't. Bizarrely, you may need a keyword to stay on the same line. If conditions, loops, and function calls don't need parens.

Basic types are Integer, Float, String, Array, Class. There's no char type, only short strings. There's also no boolean type! Instead true is TrueClass and false is FalseClass (are you starting to hate it yet?) Ruby int's can be any size (internally integers are broken into 32-bit Fixnum and unlimited Bignum).

More boolean weirdness, function names are allowed to end in a question mark, mostly used for bool-returning. A.include?(3) is a normal "contains" function. To repeat, the ? means nothing special -- it's just another letter. ! is used the same way. Functions which modify the caller should have ! on the end. w.upcase! converts w to uppercase. But again, the ! is just another letter in the name.

Python-style, variables have no type, objects do. Which means variables aren't declared. You're allowed to change types (but you get a warning):

x=6; y=7.6
w='red' # strings use double or single quotes

print(x.class, y.class, w.class) # Integer Float String

d=3<8; print d.class # TrueClass (good lord!) 

Comments use #, but a block of comments uses, I'm not joking, =begin and =end:

=begin
This is a multi-line comment
with only 2 lines
=end

Printed multi-line text has a shortcut using a crazy "pick your delimiter" << command:

# a multi-line print shortcut
print <<CAT # we've declared CAT as the sentinel
these are some
lines printed
until we see CAT
CAT
# the final CAT marks the end of the multi-line print

There are 2 common prints: print and puts. print is simple, but has no endline. puts prints each input on a line by itself (including array elements).

Capital letters

The rules for names starting with capital letters are ... odd. Class and module names must start with a capital letter. Functions can be either way. Technically cap-starting identifiers are constants, sort of; they're allowed to change but you get a warning.

Because of that last thing, if you start a class or module name with a lower-case letter the confusing error message is "must be constant".

if's

Ruby if's have the usual ==, <=, !, && and || and so on. They must end with end -- no exceptions. Cascading if's require an elsif (Ruby was created long after we knew how silly that was):

if n<9
  puts "if case 1"
elsif n==9
  puts "if case 2"
else
  puts "if case 3"
end

Crazy rule: if you want to stay on the same line, you need an extra then. Arrg. Also note there's no semi-colon required before a same-line end (special rule):

if x==2 || y<0 then print "accept" end

if !(x>l.1 && x<=10)  # no THEN beeded here
  print "inside"
end

# a same-line block with more than one statement:
if n==2 then n=0; x=9 end

Everything in Ruby is value-returning. If's included:

# assigning IF's return value:
w2 = if n==4 then "AAA" else "BBB" end

w3 = if x==0 then "zero" else if x<0 then "neg" else "pos" end end

IF's can also be after the body, in the very silly "guard" style. At least we can stop typing THEN:

n=10 if size>2

To sum uo: Ruby's IF's look like late 1950's FORTRAN.

Strangely, since if's do this already, Ruby has the value-returning ?: operator.

In a huge throwsback to stuff we rejected as silly, Ruby has the horrible unless (which can also be flipped with the body):

unless n<10 then puts "n is 10 or more" end

Strangely, or not, everything in Ruby has a true/false value. false and nil are false, the rest true:

if [0,0,0] then puts "arrays are true" end

n=0; if n then puts "0 is true"

Ruby's switch statement can check for equality, or a range, or a type:

case n
  when 3 then x=0 # THEN required only for same-line
  when 8,9 then x=6
  when 15..20 then x=7
  else x=9  # arrg! re-uses ELSE
end

As usual in Ruby, it has a return value. An example where we check for a type:

val = case n
  when String then 0 # return value
  when Integer, Float then Integer(n)
  when Array then n[0] || 0
  else 0
end

Nil checks

nil is used for null. It's a pointer to the nil object. if n==nil works but the preffered way is if n.nil? (recall the ? is simply part of the function name).

If shortcuts

The modern "quit if null instead of giving an error", which is usualy n!.val instead uses an ampersand(&):

sun.setBrightness 7  # error if sun is nil

# safely does nothing if no sun:
sun&.setBrightness 7

You also get the modern "if this would be nil, use this alternate value". Instead of using the the more common ?? for this, Ruby reuses ||:

name = w || "unknown"  # use "unknown" if w is nil

val = arr[0] || -1  # works since array-off-end is nil

# if no-one has a name longer than 6 letters, "use Freddie"
captain = players.find{|w| w.length>6} || "Freddie"

The n=a ?? 0 thing was already pointless and annoying. This manages to make it even worse by confusing it with an IF.

There's one more I'm not sure I've seen before, maybe since no one else wanted it. ||= assigns only if you're nil (or false, but when would that happen?):

n = w.index('x')  # nil if not found
n ||=0

# if n==nil n=0  # same things
# n=0 if n==nil

Loops

Ruby has the basic while, and the pointless do-while. It even brings back the until loop. Here's a while, nothing special except the same-line symbol is DO instead of THEN:

# basic while loop:
n=1
while n<20 do print "#{n}, "; n*=2 end

#same, on multiple lines:
while n<20
  print "#{n}, "
  n*=2
end

Of course Ruby's primary loop is an implicit foreach, over lists or ranges:

# list foreach (nothing special):
for i in [5,8,9]
  print i*10
end

# range foreach, using (same-line DO):
sum=0
for i in 1..10 do sum+=i end

3-dots in a range skips the last item. Letters are allowed ('a'..'e'). Backwards (10..1) is no good, but we can do it with downto: for 10.downto 1 do.

We have break and continue (called next):

# break and continue:
b=[13,14,15,20,50,51,84]
for v in b
  if v%2==1 then next end   # gah! so many keywords!
  break if v>50  # backwards if style
  puts v
end

There's another foreach for list/array using each. It takes a lambda function as input:

# same as foreach w:
["cat","dog","ant"].each {|w| print w,"X" }

# with a range, a little more complicated function:
(100..104).each { |cat| if cat%2==1 then print cat," " end }

# without the {} shortcut:
["cat","dog","ant"].each do |w|
  print "[",w,"]"
end

{}'s are only for lambda functions. The input(s) go inside of pipes as the 1st thing: |w| says we have 1 parameter named w. Ruby is just terrible.

More math

We have the usual +=, *=. There's no ++. Strings get *Integer. Exponentiation is **. Strings can use << as +=:

w="Arf"*3  # ArfArfArf
w << "BARK" # ArfArfArfBARK
n=2**8 # 256

For compares, Ruby adds a special -1/0/+1 operator, <=> (-1 if the first is less-than the second, and so on):

print 1<=>2," ",2<=>2," ",3<=>2,"\n"
# -1 0 +1

Triple equals is overloaded as the "in this range" operator. The range must come first:

if (1..10)===n then ... # n is betwenn 1 and 10
if (2.5..3.5) === val  # val is between 2.5 and 3.5
if ('A'..'Z') === ch  # is upper-case

Casting

Ruby has regular casts, giving an error if it can't:

n = Integer(4.8) # 4
print "abc"+String(3.1) # needed since no implicit cast to String

print Float(10)/3 # 3.333

n=Integer("abc") # error

It also has member-function casts. to_s for string, and so on:

n = 3.8.to_i # 3

print "abc"+n.to_s

Since there's no boolean type, we have to use a hack to cast to it: !!val. It's just two !'s in-a-row. !!8 converts 8 to false, then false to true (so it casts 8 to true). So silly.

Testing a type can be done with dot-class, or an overload of triple-equals where the class must come first:

if w.class == String then ...
elsif w.class == Integer then ...

if String === w then ...  # String must be first

We can also test for Array, Class, Method, Symbol, and Regexp.

Lists

As expected, Ruby arrays are just anything in []'s with commas. They allow mixed types:

Nums = [3,8,12,25]
CatInfo = ["stanley", 4.5, 6, "grey"]

A very odd rule: there's no index-out-of-bounds error. Instead Nums[99] is nil.

Ruby lists are array-backed (they should be -- their type is Array). As with most "scripting" languages, there's no Linked-List list.

Lists have the usual: slicing, + to add two of them. The special Ruby << appends an item. They're mutable:

arr=[0,1,2]+[3,4,5]
arr+=[6,7,8] # arr is 0-8
arr<<9 # 0 to 9
arr[0]=99 # legal

arr[2..4] # slice [2,3,4]
arr[2...4] # 3-dot slice still skips last one: [2,3]

Indexes (and array slices) have the standard "negative goes from the right end". arr[-1] is the last item and arr[0..-2] is everything but the last item.

Giving 2 indexes picks out a subarray: arr[2,3] is items 2, 3 and 4 (3 items, starting from 2).

They also have the usual length, insert(index, item), delete(item), delete_at(index), push, and so on. A bit odd, there's no "is this in the array" shortcut (if 3 in arr doesn't exist).

Very silly, there are shortcuts for making some types of lists. They let you skip comas, quotes, and replace {} with () (why?). For strings, start with %w:

animals = %w(cat dog rat) # same as ['cat', 'dog', 'rat']

Maps (Hashes)

Shockingly, Ruby maps work the usual way:

m = {"cat"=>4, "dog"=>8} # a 2-item HashMap
m["frog"]=9

#legal to look for missing items:
if m["cow"]!=nil then ...
if m.includes? "cow" then ...

Maps have m.keys (array of keys) and m.values, or for k in m automatically goes through the keys.

Function calls get a special map-passing shortcut. You can leave out the curly braces. This calls f with a single 2-item hash:

n=f("cat"=>4, "dog"=>8)  # f has 1 input

# or more common and weirder-looking:
n=f "cat"=>4, "dog"=>8

Functions

Functions begin with def (seriously?) and of course end with end. Parameters have no types; Ruby functions are Duck-type. A return may be omitted before the final value:

def doubleMe(n) n*2 end

def longerFunction(a,b,c)
  if a>10 then b end
  return c # this return is also optional
end

n = doubleMe(4); w=doubleMe "Cat" # anything that can use "*2"
x = longerFunc 5,20,30 # 30 (since 5 isn't more than 10)

Defualt parameters use the standard n=8 syntax. Named parameters are written into the function, forcing callers to use them (i.e.: the bad way). Defualt values of named parms omit the =:

# basic defualt parm:
def doubler(n=6) n*2 end

# named parms, with a default:
def combiner(tens:, ones:5) tens*10+ones end

n=combiner(tens:4, ones:8)  # 48
n2=combiner(tens:3)  # 35

They have the usual bonuses: *a collects extra args in an array, and **a collects extra named-args into a dictionary. It's an error for a parameter to start with a capital letter (which is a constant, so should be legal, you'd think).

A star before an array flattens it out for a call:

nums=[3,9,12]
f *nums  # same as f(3,9,12)

Tuples

There are no tuples. Seems odd, But there it is.

Classes, etc...

Ruby has 3 and a half types of classes: formal classes, simpler Struct's (somewhat obsolete), OpenStruct's -- Python-style "add any fields any time" -- and finally, the half, it's common to lazily use a mixed-type array.

Class

Member variables aren't declared -- they're created in the constructor. But to make them public we appear to declare them. All use of member variables requires a leading AT-sign @. Constructors are named initialize:

class Point
  attr_accessor :x, :y  # makes them public

  def initialize(x=0,y=0)  # constructor
    @x=x; @y=y  # assign paramters to member vars
  end

  # normal member function:
  def to_s() "(#{@x}, #{@y})" end
end

Note the many @x's and @y's. That's all normal member variable use; @x=@y+1 and so forth. The constructor can't be overloaded. Instead we do tricks with default values.

Now it gets weird. Member variables are always private. No exceptions. But attr_accessor auto-creates get/set functions (like C#'s auto-properties). So attr_accessor :x, :y makes fields effectively public (note how :x is a symbol). With that line p1.x=5 is allowed and calls a setter for x.

Creating a class object is funny. new is called from the class:

p1=Point.new(45,6)
p1=Point.new  # no inputs, so (0,0)

Private/Public

While fields are always private, member functions start out as public. private can change in two ways: by itself it applies to everything below; but at the start of a line it applies to only that function:

private def f1() ...  # appplies only to this function
def f2()  # this is back to being public
private
def f3() ...  # these are both private
def f4() ...  #
public  # turns off the private

You can have private attr-accessor :x, :y, but it won't do much. You get to use self.x (the accessor function) instead of @x.

If you really, really want to see a private field, from anywhere, use:

p1.instance_variable_get(:@x)

Again, the colon makes it a symbol amd the @ is for a member variable.

Operators & other weirdness

Overloading operators is super-easy. Use the operator as the function name:

  def +(p)  # + for point
    return Point.new(@x+p.x, @y+p.y)
  end

But there's one crazy complication: Ruby can't have what we think of as normal private variables. Ruby's private variables are for that instance only. In other words p.x in the code above is an error unless x is public (with attr_accessor). Yikes! Ruby member vars are either public, or super-private.

Private member functions are the same way; they can only be called by that instance. p1.privateFunc is always an error, even in a class function.

Statics

Ruby class statics work the usual way. static variables are always private -- only visible to member functions. The syntax was inspired by the Necronomicon. It's hacks and nonsense

class STest
  static variables begin with @@:
  @@x = 35

  # static functions begin with self-dot:
  def self.sqr(n) n*n end

  # demo of static var use:
  def incx() @@x+=1; puts "static x=#{@@x}" end
end

# class var use:
STest.sqr(6)  # 36

To check that @@x is a true class var we could call it with 2 objects: s1.incx and s2.incx. We'll get 36 and 37. They share @@x.

Struct

Structs are a hack to quickly make a data-only class. These days, it's probably better to make a class with attr_accessor. A struct:

# define struct Cat:
Cat = Struct.new(:name, :age) # list fields as symbols

# create cats as normal:
c1=Cat.new("fluffy",5)
c1.name="Biff";

Cat is actually a class, a subclass of class Struct with an overloaded new and tricks to create your public variables.

We can add member functions to a Struct, but it's weird. Add do and end and write functions with no parameters which use the members without the @ (Gah!):

Cat = Struct.new(:name, :age) do
  def info() "Name #{name}, Age: #{age}" end
  def monthsOld() age*12 end
end

Again, skip these and just use a class.

OpenStruct (Map-style class)

The last is the "it's really a hash on variable names" class. Not much to it. There's no constructor since you add whatever you need later:

# a Ruby-style include:
require 'ostruct'

d1=OpenStruct.new
d1.hatSize=7  # make up whatever you want
d1.hair="wavy"

A nice feature is how checking a non-existant item merely gives nil: if d1.feather!=nil then is just fine.

Inheritance

As usual, you don't need to inherit for polymophism (since we have all we need with Duck-typing). But you can. Use a less-than to inherit (yes, really). All functions are virtual so overriding is automatic. A funny thing: any function can use super, alone, to call the function it overrides. Give it inputs if it needs them.

Assume Pig has 2 inputs. This new Pig adds yummyness:

class ChesterWhite < Pig
  def initialize(w,s,y)
    super(w,s) # Pig initialize
    @yummyness=y
  end

  def info # override, super calls Pig.info:
    "#{super} Yum: #{@yummyness}"
  end
end

The usual p2 = ChesterWhite.new(102, 3, "very") will declare a chesterWhite pig.

A little more on super. Supose we have def gluk(n) ... in a class. A subclass can override it and do:

  def gluk(n)
    baseVal=super(m)  # galls gluk(n) on superclass
    # now play with it

Namespaces

Keyword module can create a namespace. It can have anything, but only for classes will be visible. Double-colon looks it up:

module CowStuff
  class Horns ... end
  class Chewing ... end
end

c1=CowStuff::Horns.new

Or, in a completely insane rule, a module can hold pastable member functions. A class may include the module to get those functions. They may even refer to as-yet-unknown member variables:

module ShowN
  # this assume that whomever includes it will have @n:
  def showVar() print "var=",@n end
end

class AA1
  def initialize(n) @n=n end

  # a shortcut to put the showVar function into AA1:
  include ShowN
end

That's nuts. Ruby is supposed to be OOP; it should inherit these things like normal people.

include can also be used the normal way -- include Animals allows you to use any classes in it, unqualified.

require 'filename' is the command to load other files. require './frog' grabs frog.rb from the current directory, just like it was part of the current file (in other words, files don't auto-create namespaces for themselves the way Python does).

"Global" variables are just odd. Variables are normally never exposed outside of their class/module. But adding $ in front makes it a "global". These can be seen, with no extra look-up, from anywhere. $yam="white" anywhere allows anyone anywhere else to use just $yam to see it. This seems really bad.

Passing functions, anon functions, closures

Passing functions works the usual way, but with more syntax. Calling a function variable is f.call(n). Yuck. Anonymous functions are passed as normal, but existing function f must be passed as method(:f). Yuck-yuck. Here's a function taking a function as input which counts certain items in a list:

def countSome(f,arry)
  sum=0
  for n in arry
    if f.call(n) then sum+=1 end # <= f.call(n), not f(n)
  end
  sum # return value
end

So far only 1 extra thing, that silly f.call. Now let's run it with a pre-made oneToTen boolean function:

# A simple bool func (the ? is decoration only):
def oneToTen?(n) n>=1 && n<=10 end

c1 = countSome( method(:oneToTen?), nums )

Anonymous functions don't need that, but writing them is really weird. The inputs go inside of pipes, with the whole thing wrapped in curly-braces (the only place curlies are used):

even = lambda {|n| n%2==0}

# OR...
even = lambda do
  |n|
  n%2==0
end

# lambdas are used directly:
c1 = countSome(even, nums)

Strangely, we're not allowed the ending-? style with lambdas. Naming it even? would be an error.

Ruby can capture variables in the usual way. A typical curried x+y example (note all of the call's):

# nested lambda:
adder = lambda {|x| lambda {|y| x+y }}

# create a function with x captured as 2:
plus2=adder.call(2)
print "plus2=",plus2.call(6)," ",
  adder.call(5).call(4) # how to supply both inputs

The trick also works with normal def functions. This creates an "are you in this range" function:

def inRange?(low, high) lambda{|n| n>=low && n<=high} end

c1 = countSome(inRange?(40,60), nums)

is1to10 = inRange?(1,10)
if countSome(is1to10, nums)==0 then ...

Then we get two more super-silly ways to do the same thing. For one, every function can automatically be passed a block that comes after the end of the call, yield in the function runs it. Here doLots expects a block, which it calls for every item in a sequence:

def doLots(i0, i1)
  for n in i0..i1
    yield n  # calls the 1 special block we were passed
  end
end

To use a function like this, we call it with our block afterwards. This call prints each number in the range with a > in front:

doLots(3,6) {|a| print ">",a }

Many Ruby built-ins use this style -- the parameters don't list a function to run, you just have to know it comes afterwards. Other languages use this style, except you at least list the block, with a name, and call it like a normal person.

The other super-silly one looks like a lambda but has extra junk. First, it's created with Proc.new instead of lambda:

# p1 is another stored function:
p1 = Proc.new {|x| x*2}

Next the function using it needs an & before the parameter, and the call does also:

def useProc(&pr)  # & in front is required for "Procs" 
  print pr.call(8)
end

useProc(&p1)  # passing a "Proc" also requires the &

Procs have the special ability to use return to completely escape the function calling them. That hardly seems worth the effort.

Regular expressions

Ruby regexps are almost completely standard. ^\W{2,3} looks for 2 or 3 alphanumeric characters at the start, and [a-z]+7..\d looks for at least 1 lowercase letter, a "7", two wildcards and any number. Ruby even has the () groups trick.

Things to know: slashes go around a reg-exp, like /ca.t/, and =~ checks them, returning nil or the first spot where it matched:

i= "rat cat cantor: =~ /ca.t/  # 8 (start of "cant" matching ca.t)
They often look like garbage, due to the fighting slashes, for example /\d{3,5}.\d/.

Strings have some really fun regexp builtins. w[regexp] returns the first match. w.gsub(regexp]{block} runs the block on every match:

# NOTE: *? is the non-greedy "match shortest" version of *:
'the XcowX is here'[/X.*?X/]
# XcowX

w='the XcowX is in XcaveX now'
w.gsub(/X.*?X/) {|w| puts w[1..-2]}
  cow
  cave

Note to self: how to put that {|w|...} into a variable and use it? Because that won't work now.

Symbols

Symbols are a general-purpose trick. Technically they're just immutable strings. They can replace strings, or be used to hack-up enums. The method function requires a symbol, which means that tricks with passing functions often require symbols. Anything with a colon in front is a symbol. The characters themselves can be just anything. :abc is a symbol.

A sort of enum with symbols:

# put them in an array, since why not?:
carTypes=[:car, :truck, :van, :sedan]

ct1 = :truck
if ct1 == :sedan then ...

What's the advantage? Well, it's easier to read than numbers, and just as fast (symbols are compared by object id). I suppose code-completion can help better than with strings. It's better than nothing, but it seems really odd that Ruby has no real enums.

Symbols are also popular as a string replacement in Hashes, merely for speed. Which seems odd since Ruby isn't generally concerned with speed. Here :red and :purple symbols represent RGB colors:

colors={:red=>[255,0,0], :purple=>[255,255,0]}
rgb = colors[:red]

Again, this is the same as using "red"=> and colors["red"]. Just a tad faster (and more Rubyish).

Assigning a pre-written function to a variable requires symbols. The problem it solves is that Ruby doesn't require parens, so calling doSomething(f) thinks you want to call f() first. The fix is making its name into a symbol and converting that to a method: doSomething(method(:f)).

We can turn strings into symbols with dot-to_sym. This allows us to call functions given their names as strings::

w="oneToTen?"
method(w.to_sym).call(12)  # runs "oneTo10?"(12)

For fun, using method in a class goes on the last part:

w="sqrt"
op = Math.method(w.to_sym)  # op is Math.sqrt
n = op.call(25)  # 5