From Java to Ruby

2017-01-08 7li7w ruby

One of my 2017 resolutions is to try different languages and see how they compare to Java. This is inspired by the 7 languages in 7 weeks book by Bruce A. Tate. I don’t expect to learn a new language each week though, since it took me several weeks only for the first one.

The first language I’ve tried is Ruby. I’ve already seen Ruby used many times: Vagrant configuration, Logstash plugins, FluentD plugins, web applications like Kibana 2…​ But I don’t have any experience in Ruby development. So I used the Ruby Koans to teach myself Ruby.

The good

Syntactic sugar everywhere

Ruby has a lot of syntactic sugar which makes code really easy to read, and short to write.

The first example deals with arrays:

a=["one","two","three","four"]
puts a[1..-2] (1)

first, *tail = a (2)
1 Prints two and three whose indices are between 1 and length-2
2 The array is destructured: first contains one and tail an array of two, three, four

The second example shows the templating system in strings:

i=4
puts "Square root of #{i} is #{Math.sqrt(i)}" (1)
1 Prints Square root of 4 is 2.0

Now let’s see getters and setters. By default attributes a private, while methods are public:

class Person
  def name            (1)
    @name
  end
  def name=(new_name) (2)
    @name=new_name
  end
end

p=Person.new
p.name="John Doe"     (3)
puts p.name           (4)
1 Declare a getter, return is optional
2 Declare a setter, notice this funny method name name=
3 Call the setter
4 Call the getter

I’ll show later that getters and setters can be generated.

Meta programming

Ruby has a powerful API similar to Java reflection:

class Talkative
  def say_hello
    puts "Hello"
  end
  def method_missing(method_name, *arguments, &block)
    puts "Call #{method_name} with #{arguments}" (1)
  end
end

s = Talkative.new
s.say_hello
s.say("Goodbye")

As the say method is not implemented, the method_missing is called. You will notice that a method call is made of a method_name, an array of arguments, and a code block (I’ll tell more on that later).

Hello
Call say with ["Goodbye"]

The above example shows how to generate methods in an existing class. With this API, creating a proxy to wrap an object and do some kind of AOP is simple:

class Proxy
  def initialize(target_object)
    @object = target_object
  end
  def method_missing(method_name, *arguments, &block)
    if @object.respond_to?(method_name)                      (1)
      puts "Before #{method_name} #{arguments}"
      result = @object.send(method_name, *arguments, &block) (2)
      puts "After #{method_name}: #{result}"
      return result
    end
  end
end

s = Proxy.new("Hello world!")                                (3)
puts s.upcase
1 Check whether the target object has the method or not
2 Call the method on the target object.
3 Wrap the string in a proxy
Before upcase []
After upcase: HELLO WORLD!
HELLO WORLD!

The bad

Strings

Strings are mutable:

hi = "Hello "
hi << "world"

The above example is similar to a Java StringBuilder: at the end hi contains Hello world. I know that having immutable Strings in Java allows many optimizations in the JVM, I wonder what’s the performance penalty of having mutable strings.

Multiline strings is something I miss in Java, but Ruby’s syntax is rather strange. They are delimited with %{ …​ } or %! …​ ! or …​:

multiline = %{
First line
Second line
}

Unless

I am not a big fan of the unless keyword which does the contrary of if

rude = false
unless rude    (1)
  puts "Please"
end
1 Is the same as if !rude

Sticking to the if keyword looks like better option to me.

However, I have to admit that this syntax is not as bad:

rude = true
child.say_hello("mister") unless rude

Functional programming

Doing functional programming with Ruby is clearly possible. But it doesn’t look natural to me.

First the lambda functions syntax could be simpler:

twice = lambda { |x| 2 * x } (1)
puts twice.call(3) (2)

twice = lambda do |x| (3)
  2 * x
end
1 Means x → 2 * x
2 The call method is used to invoke the function
3 the { …​ } delimiter can be replaced by do …​ end for multiline functions

Then, passing functions to methods uses a special feature. Methods can receive a block of code as a special argument, this block is then called using the yield keyword.

class Demo
  def say_hello
    if block_given?
      message = yield("Hello World!") (2)
    else
      message = "Hello World!"
    end
    puts message
  end
  def yell_hello
    say_hello { |s| s.upcase } (1)
  end
end
1 Invoke the say_hello method if a block of code
2 Invoke the block and pass it some parameters

I first wondered why the block wasn’t passed as other method arguments, and why it was handled as a special argument. The reason is probably that it allows do "sandwich code":

open(file_name) do |file| (1)
  # Do something with opened file
end (2)
1 Open the file named file_name and make it available to the block.
2 Close the file

The open method takes two arguments the file name and the code block dealing with the file. In the end, this looks like Java’s try-with-resources statement.

Open classes

Classes are open which means, anyone can add a method to any class, even system ones:

class ::Integer      (1)
  def even?          (2)
    (self % 2) == 0
  end
end

puts 1.even?         (3)
1 Let’s add a method to integers
2 Funny fact: in Ruby’s naming conventions method returning a boolean should end with ?
3 Prints false

While it can be useful, placed in wrong hands, this weapon may be harmful. Yet existing methods can not be replaced.

Building and packaging

Ruby libraries and applications are packaged as Gems which are Zip archives containing code and metadata, a bit like Java’s Jar files.

To start a Ruby project, 4 tools are needed:

  • Ruby: there are multiple versions and variants (MRI, JRuby…​) to choose from. RVM (Ruby Version Manager) is an optional tool to install and activate the appropriate one.

  • Gem is the tool to download gems from http://rubygems.org (which is a sort of Maven Central repository) and unzip them in the local repository. It’s a package manager like Yum, NPM…​

  • Bundler takes a bill of materials, the list of dependencies for the project, and runs Gem to download and install them.

  • Rake (Ruby Make) is Ruby build tool, it can process files, run unit tests…​

And 2 files are need to describe a project:

  • Gemfile contains the list of Gems needed to build and run the project. It’s used by Bundler.

source 'https://rubygems.org'

gem 'rspec'
gem 'rspec-collection_matchers'
  • Rakefile describes how to build the project. It’s Rake configuration file.

begin
  require 'rspec/core/rake_task'

  RSpec::Core::RakeTask.new(:spec)

  task :default => :spec
rescue LoadError
  # RSpec not available
end

For the newcomer, starting a project is not a trivial task. I had to read each tool documentation independently, look for an example of each config file and assemble the whole.

The odd

Objects have a numeric Id which can be used for reference comparison:

o=Object.new
puts o.object_id (1)
1 The object Id is a numeric value, for instance 4376500

Symbols

I had hard time understanding the concept of Symbol.

A symbol is a kind of immutable and unique string which can be used as a constant or as a label.

color = :red
color = :blue

In this example, red and blue are symbols used as enum values.

Symbols are used with meta programming to identify methods, attributes and so forth:

class Person
  attr_reader :id  (1)
  attr_accessor :name (2)
end
1 The id attribute is read-only, it has a setter
2 The name attribute is read/write, it has a getter and a setter

Symbols are also used as hash map keys because they are unique:

ages = { :bob => 23, :john => 35 }
ages = { bob: 23, john: 35}

Since Ruby 2, the two above hashes are identical.

Modules

Modules are a bit like Java packages, they are used as namespaces.

The weird part, is that a module can contain classes and a class can contain modules:

module Demo (1)
  class Thing
    module Nameable (2)
      def set_name(name)
        @name = name
      end
    end
  end
  class Pet < Thing
    include Thing::Nameable
  end
end

medor = Demo::Pet.new
medor.set_name("Médor") (3)
1 The Demo module is used as a container for Thing and Pet classes.
2 The Nameable module is used as a mixin.
3 The Pet class as set_name method.

In fact a module and a class are close concepts, a class is a module which can be instiated.

Conclusion

Ruby is close to Java because it has the same OOP roots: both are inspired by Smalltalk, both were born in the nineties.

I don’t pretend to be a Ruby expert at all, if I wrote something wrong, tell me.