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

One Response to “a fluent interface for numeric literals”

  1. Paul Wilson Says:

    Hi Kevin,

    Paul from Agile Scotland here. I’ve had a shot too. Here’s mine.

Leave a Reply