Flexing Your Message Centric Muscles

Objective

To learn that everything is an object in Smalltalk but not in Ruby.

Discussion

In this article we will explore : How far can we push Ruby to be as message centric as Smalltalk? David Black has a good presentation on YouTube that I have listed in the references section. But the problem with his presentation is that he goes off track. I want to push the limits of Ruby and see how close we can get to Smalltalk. He failed to do so in his presentation.

If-Else

The conditionals such as if-else statements are not objects in Ruby.

Ruby Version

count = 0

if (count < 10)
  print "Count is less than 10"
else
  print "Count is greater than or equal to 10"
end

Smalltalk Version

The if-else conditional is implemented as sending messages to the Boolean objects in Smalltalk.

(count < 10)
   ifTrue: [ Transcript show: "Count is less than 10"]
   ifFalse: [ Transcript show: "Count is greater than or equal to 10" ]

In Smalltalk, there is a concept called cascading. You can see how the ifTrue and ifFalse messages are chained together.

Message Centric Ruby Version

This solution is stolen from Sandi Metz presentation Nothing is Something.

class TrueClass
  def if_true
    yield
    self
  end

  def if_false
    self
  end
end

class FalseClass
  def if_false
    yield
    self
  end

  def if_true
    self
  end
end

(count < 10).if_true{ print "Count is less than 10" }.if_false { print "Count is greater than or equal to 10" }

Solution by Xiong Xiong:

module IfElse
  def if_true(&blk)
    # if self.class.ancestors.include? TrueClass
    yield if self.is_a? TrueClass
    self
  end

  def if_false(&blk)
    # if self.class.ancestors.include? FalseClass
    yield if self.is_a? FalseClass
    self
  end
end

class TrueClass
  include IfElse
end

class FalseClass
  include IfElse
end

count=1
(count < 10).if_true { puts "True" }.if_false{ puts "False" }

count=100
(count < 10).if_true { puts "True" }.if_false{ puts "False" }

Seek Roles not Abstraction

In her presentation, Sandi Metz she says 'Abstraction Seeking'. I would say Role Seeking. Because her example illustrates the different roles. It is too vague to say 'Abstraction Seeking'. It is much more concrete to say 'Role Seeking'. Objects like User, Customer are vague. They probably play many roles. You will come up with a better design if you seek to find the roles.

Case Statement

The case statements are not objects in Ruby.

Ruby Version

character = ''

case character
when "^"
  puts 'It is a caret'
when ">"
  puts 'It is a greater than'
else
  puts "You can't even use a computer!"
end

Smalltalk Version

The case statements is implemented as a Dictionary that allows sending messages to objects in Smalltalk.

map := Dictionary new.
map at: $^ put: [Transcript show: 'It is a caret'].
map at: $> put: [Transcript show: 'It is greater than'].
map at: $< put: [Transcript show: 'It is less than']
         :
result := (map at: char) value.

Message Centric Ruby Version

character = '>'

hash = Hash.new
hash['^'] = -> { print 'It is a caret' }
hash['>'] = -> { print 'It is greater than' }

result = hash[character].call

Solution by Steven Chanin:

class Caser
  def initialize
    @conditions = Hash.new
  end

  def add(value, &block)
    @conditions[value] = block
  end

  def at(value)
    match = @conditions[value]

    if match
      match.call
      :matched
    else
      :no_match
    end
  end
end

# Testing

c = Caser.new
c.add("^") do
  puts "It's a caret"
end

c.add(">") do
  puts "It's a greater than"
end

c.add("<") do
  puts "It's a less than than"
end

puts c.at('<')
puts c.at('>')
puts c.at('^')
puts c.at('*')

Solution by Richard Lau:

class String
  DICTIONARY = {
    "^" => Proc.new{ puts "it's a carat" },
    ">" => Proc.new{ puts "it's a greater or less than sign" }
  }

  def check
    if DICTIONARY[self]
      DICTIONARY[self].call
    else
      puts "You can't even use a computer!"
    end
  end
end

"^".check
">".check
" ".check

Solution by Xiong Xiong:

class BasicObject
  def add(key, &blk)
    @my_at||=::Hash.new
    @my_at[key] = blk
  end

  def at(key)
    @my_at||=::Hash.new
    @my_at[key].call if @my_at[key]
  end

end

map = Hash.new

map.add("^") { puts 'It is a caret' }
map.at("&")
map.at("^")

Loop

Loops are not objects in Ruby.

Ruby Version

loop do
 p 'This will print forever'
end

Smalltalk Version

Smalltalk sends a repeat message to a Block object. The structure is :

[ code ].repeat

So, it would be like this:

[Transcript show: 'This will repeat forever'] repeat

Loops are inherently message centric in Smalltalk.

Message Centric Ruby Version

class Proc
  def repeat
    loop do
      call
    end
  end
end

forever = -> { p 'This will print forever' }

forever.repeat 

Solution by Richard Lau:

class Proc
  def repeat
    self.call while true
  end
end

prc = Proc.new{ puts 'This will print forever' }

prc.repeat

While Loop

The while loops are not objects in Ruby.

Ruby

n = 1
while (n <= 10) do
  p n
  n += 1
end

Smalltalk Version

In Smalltalk, the while loop is implemented as objects.

n := 1.
[n <= 10]
    whileTrue:
       [Transcript show: n.
n := n + 1].

In Smalltalk Blocks are objects. The square brackets is used for blocks.

Message Centric Ruby Version

class Proc
  def while_true
    while self.call
      yield
    end
  end
end

n = 1

my_proc = -> { n <= 10}

my_proc.while_true do
  p n
  n += 1  
end

Since blocks are not objects in Ruby, we need to convert the block to a Proc object. We can open the Proc class and define while_true method that encapsulates the while looping logic.

Summary

In this article, we compared the language constructs that are object oriented in Smalltalk with their Ruby equivalent. We made the non OO Ruby language constructs into message centric calls by adding methods to Proc, Hash, TrueClass and FalseClass.

References

  1. Smalltalk and Object Orientation by John Hunt
  2. Nothing is Something presentation by Sandi Metz
  3. The Well Grounded Nuby by David Black.


Related Articles


Software Compatibility Best Practices

I spoke to some of the most talented and experienced software developers. I have created a guide that is filled with valuable insights and actionable ideas to boost developer productivity.

You will gain a better understanding of what's working well for other developers and how they address the software compatibility problems.

Get the Guide Now