From: Logan Capaldo Date: 2005-09-20T08:30:15+09:00 Subject: [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__