Thursday 22 October 2009

Duck-typing

Let us say I want to be able to output a variety of objects (say strings, floats and dates) on a web page. With Java I would create a new class with over-loaded methods, like this (warning: my Java is getting rusty, and this is untested):
class Formatter {
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yy");

public static String show(String s) { return s; }
public static String show(Date d) { return sdf.format(d); }
public static String show(double x) { return x < 0.05 ? '<0.1' : "" + (Math.round(x * 10) / 10.0); }
public static String show(Object o) { return o.toString(); }
}

To invoke, I would use this:
Formatter.show(myObject);

Java will select the method based on the class I send. Note that there is a method for object to catch anything unexpected.

In Ruby, I would approach this quite differently. There is no need for a new class, just modify the existing classes. This is not possible in Java; I could extend Date, but I would have to ensure that every date I sent was of my date class. String and float cannot be extended at all.
class String
def show
to_s
end
end

# Remember the require 'date.rb'
class DateTime
DATE_FORMAT = '%d/%b/%y'

def show
strftime(DATE_FORMAT)
end
end

class Float
def show
self < 0.05 ? '<0.1' : (self * 10).round / 10.0
end
end

class Object
def show
to_s
end
end

That is more verbose, but the result is much neater, much more object-orientated, as now the method can be invoked like this:
my_object.show

I have a library of useful Java methods. It is a collection of static methods that do various operations on arrays and strings. In Ruby, I am building up a library that changes how arrays and string behave. Just occasionally, that is not the best way - again because of duck-typing, as it happens. Say I have a method to format dates consistently. All it does is invoke strftime with a certain format string. I could define my method as part of the DateTime class, however I would then be unable to use it with Time objects. In this case, I am better adding the method to the Object class, then DateTime, Date and Time objects would all be able to use it, as they all have strftime methods (and duck-typing allows this to work).

Wednesday 21 October 2009

The Case Statement and Relationship Operator

Ruby supports a case statement, in which the value of something is matched against a set of options
case value
when 1, 2, 5
do_this
when 3
do_that
else
do_the_other
end

In this example the first when will catch three different values. Note that unlike the C family of languages, there is no break statement used. Options cannot fall though to the next one.

You do not need to give a parameter to the case statement, as seen here.
case
when @t == 7
p 't is 7'
when @s == :this
p 's is :this'
else
p 'none of the above'
end

In this form, the case is like an if/elsif chain.
if @t == 7
p 't is 7'
elsif @s == :this
p 's is :this'
else
p 'none of the above'
end

So why use case? Well, case returns a value, so instead we could do this:
s = case
when @t == 7
't is 7'
when @s == :this
's is :this'
else
'none of the above'
end


The Relationship Operator

The case statement uses the relationship operator, === (aka triple equals or trequals operator) when comparing the value to each when. The relationship operator is really a method, and in Object the relationship operator is defined to do the same as the equals operator. However, the important point here is that it can be overridden as required. Patterns override it to check for a match, and the Range class overrides it to check if the value is within the range. That allows you to do things like this:
mark = 56

grade = case mark
when 1..25
'Fail'
when 26..50
'C'
when 51..75
'B'
when 76..100
'A'
else
'Out of range!'
end

Here grade is set to 'B' because (51..75) === 56 evaulates to true. Note that this is calling the === method on 51..75. Write it the other way around, 56 === (51..75), and the === method of 56 is invoked, and the expression evaluates to false.

See more here:
http://www.pmamediagroup.com/2009/07/dont-call-it-case-equality/


Struggling with Ruby: Contents Page