[ANN] MLTypes: ML-style qualified unions for ruby

I've always liked ML-style languages for declaring new types, and I thought hey, let me try to do that in ruby, its already got a case statement that reminds me of ML. So on with the examples:

% cat example.rb
require 'mltypes'

deftype :List do
         :Cons.of( :first => :Object, :rest => :List) | :End
end

deftype :Tree do
         :Leaf.of(:data => :Object) | :Branch.of( :data => :Object, :left => :Tree, :right => :Tree )
end

def print_list(list)
         case list.tag
         when :Cons
                 puts list.first
                 print_list(list.rest)
         when :End
         end
end

def print_tree_inorder(tree)
         case tree.tag
         when :Leaf
                 puts tree.data
         when :Branch
                 print_tree_inorder(tree.left)
                 puts tree.data
                 print_tree_inorder(tree.right)
         end
end

ls = List.Cons(:first => 1, :rest => List.End)
ls = List.Cons( :first => "Hello", :rest => ls )
print_list ls

tree = Tree.Branch( :data => 2, :left => Tree.Leaf( :data => 1 ), :right => Tree.Leaf( :data => 3 ))

print_tree_inorder(tree)

__END__

Not bad huh?

The other interesting thing to note (for me anyway is)

deftype :MyBoolean do
      :Yes | :No # bar works here
end

:maybe | :so # not here though
NoMethodError: undefined method `|' for :maybe:Symbol
         from (irb):6

So it shouldn't mess up any existing stuff that relies on Symbol not having |.
OTOH, its kind of heavy -handed (ie if you defined | for something else it will mess stuff up completely. Just keep that in mind)

Now here is the code that does all this fun.

% cat mltypes.rb
module MLTypes
         class Alternation
                 def initialize(alts = [])
                         @alts = alts
                 end
                 def |(other)
                         @alts << other
                         self
                 end

                 def alternatives
                         @alts
                 end
         end
         class TaggedTuple
                 def initialize(tag, pairs)
                         @tag = tag
                         @pairs = pairs
                 end

                 def |(other)
                         Alternation.new([self, other])
                 end

                 def tag
                         @tag
                 end

                 def pairs
                         @pairs
                 end
         end
end
def deftype(name)
         res = nil
         begin
         Symbol.class_eval {
                 define_method(:"|") do |other|
                         MLTypes::Alternation.new([self, other])
                 end

                 define_method(:of) do |pairs|
                         MLTypes::TaggedTuple.new(self, pairs)
                 end
         }
         res = yield
         ensure
                 Symbol.class_eval {
                         remove_method(:"|") rescue nil
                         remove_method(:of) rescue nil
                 }

         end

         Object.const_set(name.to_s, Class.new)
         klass = Object.const_get name

         klass.class_eval {
                 define_method(:tag) do
                         @tag
                 end
                 m = instance_method(:method_missing)

                 define_method(:method_missing) do |*args|
                         if not @state.nil? and @state.has_key?(args[0])
                                 @state[args[0]]
                         else
                                 m.bind(self).call(*args)
                         end
                 end

                 res.alternatives.each do |enum|
                         obj = klass.new
                         metaklass = (class << self; self; end)
                         if enum.kind_of?(MLTypes::TaggedTuple)
                                 metaklass.class_eval {
                                         define_method(enum.tag) { |args>
                                                 state = {}
                                                 enum.pairs.keys.each do |key|
                                                         raise TypeError, "Expected #{enum.pairs[key]} got #{args[key].class}" unless args[key].kind_of? Object.const_get(enum.pairs[key])
                                                         state[key] = args[key]
                                                 end
                                                 res = klass.class_eval { new }
                                                 res.instance_eval { @state = state; @tag = enum.tag }
                                                 res
                                         }
                                 }
                         else # regular enum
                         obj.instance_eval { @tag = enum }
                         metaklass.class_eval {
                         define_method(enum) do
                                 obj
                         end
                         }
                         end
                 end

                 class << self
                         private :new
                 end
         }

end

__END__