A Ruby Crash Course: Day 2

Day 2 of my foray in to Ruby. Today I will look a little deeper into the language.

tl;dr

A quick run through of operators and assignments.

More Basics – Assignment

You can perform multiple assignments. Assigning a list is:

irb(main):001:0> x = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> x
=> [1, 2, 3]

But declaring more than one variable, assigns each one a value:

irb(main):003:0> x,y,z = [1,2,3]
=> [1, 2, 3]
irb(main):004:0> x
=> 1
irb(main):005:0> y
=> 2
irb(main):006:0> z
=> 3

Square braces are optional:

irb(main):014:0> a,b = 1,2,3
=> [1, 2, 3]
irb(main):015:0> a
=> 1
irb(main):016:0> b
=> 2
irb(main):018:0> a,b = [1,2,3]
=> [1, 2, 3]
irb(main):019:0> a
=> 1
irb(main):020:0> b

The || operator is similar to the null coalescing operator, ??, in C#. If a variable is not currently set (i.e. is nil), then set it to the specified value:

irb(main):021:0> v = nil
=> nil
irb(main):022:0> v = v || "hello"
=> "hello"
irb(main):023:0> v
=> "hello"
irb(main):024:0> v = "bye!"
=> "bye!"
irb(main):025:0> v ||= "hello"
=> "bye!"
irb(main):026:0> v
=> "bye!"

Symbols

Symbols are peculiar to Ruby. In essence they are a declare-once object, which then only has a single instance no matter where it is used in a Ruby session. They are declared as follows, by prefixing an unquoted string (or quoted string with spaces), by a colon “:” :

irb(main):046:0> :foo
=> :foo
irb(main):047:0> :"hello there"
=> :"hello there"

And can be returned as strings:

irb(main):048:0> :"hello there".to_s
=> "hello there"
irb(main):049:0> :foo.to_s
=> "foo"

But cannot be assigned to once they are declared:

irb(main):054:0> :foo = :"hello there"
SyntaxError: (irb):54: syntax error, unexpected '=', expecting $end
:foo = :"hello there"
      ^
        from C:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'

Footnote to this section, in Ruby versions prior to 1.9.2, you were able to call the to_i method on a Symbol, but this is no longer valid (e.g. see here).

We can see the global symbol table, by calling Symbol.all_symbols, or just get a subset:

irb(main):078:0> Symbol.all_symbols.size
=> 3528
irb(main):079:0> Symbol.all_symbols[1,20]
=> [:"<IFUNC>", :"<CFUNC>", :respond_to?, :"core#set_method_alias", :"core#set_v
ariable_alias", :"core#undef_method", :"core#define_method", :"core#define_singl
eton_method", :"core#set_postexe", :each, :length, :size, :lambda, :intern, :get
s, :succ, :method_missing, :send, :__send__, :initialize]

Arrays and Method Naming Convention

Returning to the Array class. Consider:

irb(main):111:0> a = [1,2,[3,4, [5,6]]]
=> [1, 2, [3, 4, [5, 6]]]
irb(main):112:0> a.flatten
=> [1, 2, 3, 4, 5, 6]
irb(main):113:0> a
=> [1, 2, [3, 4, [5, 6]]]

Notice that a is unchanged. Now try:

irb(main):114:0> a.flatten!
=> [1, 2, 3, 4, 5, 6]
irb(main):115:0> a
=> [1, 2, 3, 4, 5, 6]

In general, methods that end in ! indicate that the method will modify the object it’s called on.

i.e. objects are modified in place: see this question on Stackoverflow.

Hashes

Ruby hashes, if you are familiar with Python and Perl, are very similar:

A Hash is a collection of key-value pairs. It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index. Hashes enumerate their values in the order that the corresponding keys were inserted.

Hashes have a default value that is returned when accessing keys that do not exist in the hash. By default, that value is nil.

We can declare hashes (where keys can be anything, but we’re using Symbols):

irb(main):073:0> h = {}
=> {}
irb(main):074:0> h = { :foo => "hello" }
=> {:foo=>"hello"}
irb(main):075:0> h = { :foo => "hello", :bar => "there" }
=> {:foo=>"hello", :bar=>"there"}

Appending or changing existing key/value pairs:

irb(main):080:0> h[:boo] = "hello"
=> "hello"
irb(main):081:0> h[:foo] = "goodbye"
=> "goodbye"
irb(main):082:0> h
=> {:foo=>"goodbye", :bar=>"there", :boo=>"hello"}

Simple iteration through the keys in the hash:

irb(main):084:0> h.each_key {|key| puts key }
foo
bar
boo
=> {:foo=>"goodbye", :bar=>"there", :boo=>"hello"}#

and through the values;

irb(main):085:0> h.each_value {|value| puts value }
goodbye
there
hello
=> {:foo=>"goodbye", :bar=>"there", :boo=>"hello"}

See the documentation page here for all the hash functions, e.g.

irb(main):097:0> h = {}
=> {}
irb(main):098:0> h.class
=> Hash
irb(main):099:0> Hash.instance_methods
=> [:rehash, :to_hash, :to_a, :inspect, :to_s, :==, :[], :hash, :eql?, :fetch........

Introducing The Splat Operator *

The splat operator*” does a few things. First if we define a simple function:

irb(main):027:0> def foo * args
irb(main):028:1>  args
irb(main):029:1> end
=> nil

And then call it:

irb(main):030:0> foo 1
=> [1]
irb(main):031:0> foo(1)
=> [1]
irb(main):032:0> foo(1, "hello", "world")
=> [1, "hello", "world"]

It means that we can call the method foo with varargs (or params if you’re a C# person).

Consider the multiple assignment;

irb(main):033:0> x,y = 1,2,3
=> [1, 2, 3]
irb(main):034:0> x
=> 1
irb(main):035:0> y
=> 2

Adding the splat operator to y, makes the assignment ‘greedy’:

irb(main):036:0> x, *y = 1,2,3
=> [1, 2, 3]

And we then have:

irb(main):037:0> x
=> 1
irb(main):038:0> y
=> [2, 3]

where y now contains the remaining values in the right-hand-side array.

Interesting Links: