Saturday, August 27, 2011

Ruby for you "Static Types"

Over the last month, I decided to make an honest attempt to "really" learn a new language.  For a while I had been flirting with Scala, Clojure, and Python as potential alternatives to C# and Java, but none of the languages really fit my personality and goals as a programmer.  Luckily,  a serendipitous stream of events led me to exploring the Ruby ecosystem.

I suggested the language to a couple of "static types", particularly my friend John (whom I am always referring to), and many of them scoffed.  Most had preconceptions of what Ruby is and isn't with out actually knowing much of the language and its capabilities.  John even suggested that Ruby was a language for people who program with their "pinky in the air" (for those who don't get the slight, he's inferring that Rubyists are pretentious).

I want to take the opportunity to show developers heavily ingrained in the Java and C#/VB communities some of the advanced capabilities of Ruby.  Keep in mind that I still haven't even achieved Padawan status as of this post (so if you see a better way to do something, please let me know).

INSTALLING RUBY

The best way to install Ruby on Linux or OSX (and it's not by downloading it from the main site).
  1. Install Curl (if it's not already installed).
  2. Install Git (http://git-scm.com/).
  3. Install RVM (Ruby Version Manager)
    bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
    
  4. Use RVM to install Ruby for you!
    # Show available distros and versions (there are a lot!)
    rvm list known
    # Select the latest version of the core distro
    rvm install 1.9.2
    # Create a Gem set (think namespace for the package manager)
    rvm --create use 1.9.2@roundhouse
    
If you are using Windows, use the installer from the Ruby website. You will also want to download "Ruby Gems" a the de facto package manager for Ruby.

When your install is finished, you check to ensure that it works from the shell/prompt:
ruby --version
# You should see something like this (I'm on OSX):
# ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin10.8.0]

Running a Ruby script is extremely simple.  Ruby scripts are simply text files with the "rb" extension (by convention). To run a script, use the "ruby" command followed by the file you want to run:
ruby myscript.rb

Now you should know everything you need to know to run the examples.

LANGUAGE DEMONSTRATION

I'm not going to show you basic Ruby syntax.  You will figure it out on your own.  The goal of this guide is simply to show you some of the more advanced language features.


Everything is an object (absolutely freaking everything!).

There are no primitives.

Consider this code:
puts 1.class
puts 3.14.class
puts true.class
puts nil.class
puts "String".class

class Clazz
end

puts Clazz.class

module MyModule
end

puts MyModule.class

Output:
Fixnum
Float
TrueClass
NilClass
String
Class
Module

Extension Methods?

Since everything is an object, and because Ruby is awesome, we can extend these basic language constructs:
class String

  def word_count
    words = self.split(" ").length
  end
end

word_count = "here is my string".word_count

# the "<<" is the concatenation operator for strings
# you will see that this is actually pretty gross
puts "The word count was " << word_count
And the output is:
The word count was 4
But we are not limited to extending Strings, we can extend the actual "Class" class, "Module" class, etc. This ability is similar to "extension methods" in C#, except we get much better support in what we can change to the original classes.

Operator Overloading

Just like C#, but very unlike Java, Ruby enables operator overloading:

class String

  def word_count
    words = self.split(" ").length
  end

  def -(value)
    case value
      when String
        return self.delete value
      when Fixnum
        return self.slice(value..self.length)
    end
    nil
  end

end

word_count = "here is my string".word_count
puts "The word count was " << word_count.to_s

puts "Mississippi" - "i"
puts ("Hello World" - 6)
And we see on the console:
Msssspp
World
Unlike C#, Ruby has a lot more available operators that can be overloaded, including: |, ^, &, <=>, ==, ===, =~, >, >=, <, <=, +, -, *, /, %, **, <<, >>, ~, +@, -@, [], []=, !=, !~ (thanks to: http://www.zenspider.com/Languages/Ruby/QuickRef.html)

In many cases, the meanings of these operators in the context of your class are completely up to you (I mean, what the hell does this "!~" mean to you? 'not approximately'?). Of course, Ruby is a language for "big boys"; so define these at your own risk.

Powerful Strings

String literals can be defined in a number of ways in Ruby. Typically, the way you choose is dependent on the characters you want to use.
# I need double quotes in my string
puts '"Hello World"'
# I need single quotes
puts "'Hello World'"
# I need both
puts %q{"Hello World" and 'Hello World'}
# Another alternative
puts %Q{"Hello World" and 'Hello World'}
# I need a multiline, any character string
puts <<EOF
Here is a multiline string with " and ' escaped!
I can have more content on this line
EOF
Console output:
"Hello World"
'Hello World'
"Hello World" and 'Hello World'
"Hello World" and 'Hello World'
Here is a multiline string with " and ' escaped!
I can have more content on this line
C# and Java both have their own special conventions for "formatted strings".  Some libraries in Java even handle complex expression-based logic (MVEL and SPEL).  Ruby natively supports this out of the box:
# Wow, native expressions...
answer = 42
puts "The answer to life, the universe, and everything is #{answer}"

# Expressions can are not simply variable placeholders
puts "Strings are more powerful in Ruby = #{ Time.now }"

# What?!
puts "#{ int1 = gets.to_i } + #{ int2 = gets.to_i } = #{int1 + int2}"
Output (for the last expression, I'm going to input the integers "1" and "2" in the console:
The answer to life, the universe, and everything is 42
Strings are more powerful in Ruby = 2011-08-27 21:40:15 -0400
1 + 2 = 3

Flexible Typing

Probably the most unnerving thing to the "Static Types" is the lack of interfaces.  This might lead people to believe that Ruby is full of complex class hierarchies.  This is simply not the truth!   Ruby follows the "Duck Typing" philosophy: "if it looks like a duck and quacks like a duck, it's a duck!".

This is an example of it works:
class Duck
  def quack
    puts "Duck goes 'Quack'!"
  end
end

class Goose
  def quack
    puts "Goose goes 'Quack'!"
  end
end

# Array of Waterfowl
waterfowl = [ Duck.new, Goose.new ]

# "quack" each fowl
waterfowl.each {|fowl| fowl.quack }
And our output:
Duck goes 'Quack'!
Goose goes 'Quack'!

Metaprogramming (Reflection on Steroids)

Ruby is a phenomenally reflective language.  The language provides "hooks" you can exploit within the language to do all sorts of neat things that don't really have an equivalent in Java or .NET.  These hooks allow Ruby to implement patterns that also have no equivalent in most static languages.

Variants of the Delegation Pattern

In this pattern, we are going to delegate an action to the appropriate class capable of handling the provided "context".  This is similar to the handler pattern, with one caveat.  Instead of adding the handler implementations manually to some container/composite class, we are going to use inheritance (in a very strange way).  In this case, a super class is going to register all derived classes as they are declared!
require 'forwardable'

# Weapon
class Weapon

  # Class variable to store known
  # weapon classes
  @known_weapons = []

  # Extend our class by adding
  # some variables
  class << self
    attr_reader :known_weapons
  end

  # Capture inheriting classes
  # and add them to the list of
  # known weapons
  def self.inherited(klass)
    puts "I've been inherited by #{klass.to_s}"
    # Creating a new instance
    Weapon.known_weapons << klass.new
  end

  # Attack our enemy
  def attack(enemy)
    Weapon.known_weapons.each do |weapon|
      weapon.dispatch enemy if weapon.appropriate? enemy
    end
  end

  # Is the weapon appropriate for the given enemy
  # Note: Subclasses must implement
  def appropriate?(enemy)
    raise NotImplementedError, "Subclasses must implement"
  end

  # Dispatch the enemy
  # Note: Subclasses must implement
  def dispatch(enemy)
    raise NotImplementedError, "Subclasses must implement"
  end

end

# Katana Blade
class KatanaBlade < Weapon

  def appropriate?(enemy)
    enemy == 'Ninja'
  end

  def dispatch(enemy)
    puts "#{enemy} has been sliced by the Katana Blade"
  end

end

# Ninja Stars
class NinjaStars < Weapon

  def appropriate?(enemy)
    enemy == 'Dragon'
  end

  def dispatch(enemy)
    puts "#{enemy} has been slain by my stars"
  end

end

weapon = Weapon.new

weapon.attack('Ninja')
weapon.attack('Dragon')
As we call the attack method on the weapon object, the calls are delegated to the class that can handle a particular "enemy".
I've been inherited by KatanaBlade
I've been inherited by NinjaStars
Ninja has been sliced by the Katana Blade
Dragon has been slain by my stars
The magic is found in the "self.inherited"method.  Every time a class inherits from a class with this method defined, the method is called providing a reference to the derived class.  Please note that any time the "self" keyword is used to define a method, this is known as a "Class method" (similar to static methods in C# and Java, except they are actually "instance" methods of the particular "Class" object).


Pure Awesome Pattern

OK, that's not its real name.  To be honest, I don't even know what you would call this, but it is "pure awesome".  This is why C# and Java are 'Legos', and Ruby is 'Playdough'.

Imagine writing in a language where your model is a part of the language!  I'd like to think that LINQ gets you to the 30 yard-line, but Ruby will score the touchdown:
module Vehicles

  class Make

    attr_reader :company

    def initialize(company)
      @company = company
    end

    def method_missing(name, *args)
      Model.new(name, args[0], self)
    end

  end

  class Model
    extend Forwardable

    attr_reader :model, :year, :make

    def_delegators :@make, :company

    def initialize(model, year, make)
      @model = model
      @year = year
      @make = make
    end

    def find
      Car.new(self)
    end
  end

  class Car
    extend Forwardable
    def_delegators :@model_container, :company, :model, :year, :make

    def initialize(model)
      @model_container = model
    end

    def to_s
      "#{year} #{company} #{model}"
    end
  end

  class CarCatalog
    def method_missing(name, *args)
      Make.new(name)
    end
  end
end

catalog = Vehicles::CarCatalog.new
car = catalog.Nissan.Frontier(2007).find
puts car
And we get:
2007 Nissan Frontier
Right now you are probably thinking, WTF!? The magic is in the "method_missing" function. Whenever a method is called on a class and it does not exist, Ruby will look to see if the class defines the "method_missing" function. If it does, Ruby will send the name of the "method that is missing" along with it's parameters to that method.

From there, we can do whatever we want. In this case, we are going to use name of that method that was missing as the name of our car "make" and "model". In this case, we are using "Nissan" and "Frontier". If you would like a better (perhaps a bit more practical example), look at the MongoDB driver for Ruby which is quite elegant (and through me for a complete loop when I first saw it).

Finalize, Disposed...Meet "at_exit"

The final "hook" I would like to demonstrate is the "at_exit" global method that runs a "code block" when the program is terminating.  A "code block" is a Ruby construct similar to a "lambda" in C#.  Like lambdas code blocks keep scope of the variables around them.

at_exit do
  puts "Program has ended"
end

class Apocalypse

  def initialize(message)
    @message = message
    at_exit { sound_horn }
  end

  def sound_horn
    puts @message
  end
end

Apocalypse.new("The end is nigh!")

puts "Right before termination"
Here's the output, pay special attention to the order:
Right before termination
The end is nigh!
Program has ended

CONCLUSION

So that's a tidbit of Ruby.  I must mention that I only showed you a fraction of the language, and I am only in my first month of study.  I hope that if you are wandering like I was (in search of a language), that you might find a home in the Ruby community.  Ruby is an extremely powerful language, and despite common misconceptions, can be incredibly fast depending on the Ruby runtime (and there are several).  More importantly, the Ruby community is phenomenal.  Many of the best Java and C# libraries being fielded today are ports of Ruby frameworks.

If you are an experienced programmer and looking to get a jump start into the Ruby ecosystem, I highly recommend that you skip the beginners books and jump directly into (I should add that Russ Olsen is a pretty funny read in addition to providing excellent content):

3 comments:

  1. My RSS reader cut off as you were about to reveal which books to purchase, forcing me to actually visit your blog. Well played.

    This is a nice intro. I didn't think you were /overly/ pretentious as you explained why Ruby is better than oxygen, coffee, and unicorns. =)

    ReplyDelete
  2. So far, the number of points and ideas given here are actually what I was looking for from the last few hours, hopefully this would proved to be the best guide, if further information taken out from the same platform to assist in more efficient way. Check http://great-college-paper.com/ for best Papers

    ReplyDelete
  3. All the explanations you made, the simple site menu, the relationships you can give support to instill - it's got many fantastic, and it's facilitating our son and our family recognize that this theme is awesome, and that's exceptionally essential. Many thanks for everything! Also look forward party boxes for best Party boxes.

    ReplyDelete

Note: Only a member of this blog may post a comment.