a fluent interface for numeric literals
December 19, 2007
In The Cardinality of a Fluent Interface Michael Feathers posed a code kata in which one has to create a DSL in which constructs such as the following all provide the expected numeric value:
one two twelve twelve.hundred one.hundred.thousand.and.fifteen twelve.hundred.and.one five.hundred.and.fifty.seven
My first pass at a solution in Ruby is listed below (and a copy with unit tests can be downloaded from my website). There’s still some duplication in there, and hopefully I’ll get around to removing it soon.
In his post, Mike asks whether the DSL’s “cardinality” — the number of classes required to implement the DSL — says anything about the DSL’s grammar. Well, my solution has 7 classes (although PartialValue exists only to remove a little duplication). I have no idea what that might mean in the DSL world.
Anyway, here’s the Ruby code:
module Literals
Ints = {:one => 1, :two => 2, :three => 3, :four => 4, :five => 5,
:six => 6, :seven => 7, :eight => 8, :nine => 9}
Ten = { :ten => 10 }
Teens = {:eleven => 11, :twelve => 12, :thirteen => 13,
:fourteen => 14, :fifteen => 15, :sixteen => 16,
:seventeen => 17, :eighteen => 18, :nineteen => 19 }
Tens = {:twenty => 20, :thirty => 30, :forty => 40,
:fifty => 50, :sixty => 60, :seventy => 70,
:eighty => 80, :ninety => 90 }
class PartialValue
def initialize(val) @value = val end
end
class NumericLiteral < PartialValue
include Comparable
attr_reader :value
def (other); @value other.to_i; end
def to_i; @value; end
def thousand; Hundred.new(@value * 1000); end
end
class Numty < NumericLiteral
Ints.each do |s,v|
define_method(s) { Unit.new(@value + v) }
end
end
class Unit < NumericLiteral
def hundred() Hundred.new(@value * 100) end
end
class Hundred < NumericLiteral
def and() Adder.new(@value) end
(Ints.merge(Teens)).each do |s,v|
define_method(s) { HundredExpecter.new(value + (v*100)) }
end
end
class Adder < PartialValue
(Ints.merge(Ten).merge(Teens)).each do |s,v|
define_method(s) { Unit.new(@value + v) }
end
Tens.each do |s,v|
define_method(s) { Numty.new(@value + v) }
end
end
class HundredExpecter < PartialValue
def hundred() Hundred.new(@value) end
end
(Ints.merge(Teens)).each do |s,v|
define_method(s) { Unit.new(v) }
end
def ten; NumericLiteral.new(10); end
Tens.each do |s,v|
define_method(s) { Numty.new(v) }
end
end







December 22, 2007 at 11:14 pm
Hi Kevin,
Paul from Agile Scotland here. I’ve had a shot too. Here’s mine.