Romans, rubies and the D

Posted on by Idorobots

There's an increasing interest with the D programming language amongst my readers so I figured I'll post a bunch of short posts about D and see what happens.

Anyway, here's a classic example showing Ruby's capabilities taken from Seven Languages in Seven Weeks:

class Roman
  def self.method_missing name, *args
    roman = name.to_s
    roman.gsub!("IV", "IIII")
    roman.gsub!("IX", "VIIII")
    roman.gsub!("XL", "XXXX")
    roman.gsub!("XC", "LXXXX")

    (roman.count("I") +
     roman.count("V") * 5 +
     roman.count("X") * 10 +
     roman.count("L") * 50 +
     roman.count("C") * 100)
  end
end

puts Roman.X
puts Roman.XC
puts Roman.XII

This snippet creates a simple Roman numbers API using Ruby's method_missing - it basically translates method name to its decimal value. For simplicities sake it misses error checking and the like.

Here's my version in the D programming language:

import std.stdio;
import std.array;
import std.algorithm;

struct Roman {
    @property static auto opDispatch(string number)() {
        enum num = number.replace("IV", "IIII")
                         .replace("IX", "VIIII")
                         .replace("XL", "XXXX")
                         .replace("XC", "LXXXX");

        enum value = num.count('I')
                   + num.count('V') * 5
                   + num.count('X') * 10
                   + num.count('L') * 50
                   + num.count('C') * 100;

        return value;
    }
}

void main() {
    writeln(Roman.XV);
    writeln(Roman.IIX);
    writeln(Roman.VXCII);
}

Again, for the sake of simplicity it doesn't check for any errors and whatnot. This code works the same way Ruby's version does. Using opDispatch (D's version of method_missing) it first translates the method name into a simpler form and then counts different characters obtaining the decimal value.

The biggest and most significant difference is the fact, that D's version has no runtime overhead at all!

D's opDispatch is a template, meaning that it's instantiated at compile time making string number a compile time value. Now, making a use of such a compile time value and assigning the result to an enum triggers CTFE in D - the code is run at compile time leaving no runtime overhead at all!

(Obviously there might be some overhead in opDispatch!RomanLiteral() call, but it will most likely be inlined by the compiler.)

We end up having a simple, readable API using high level features in a systems programming language that's as fast as C. This is one of the numerous reasons I like D so much.

2016-02-16: Adjusted some links & tags.