Isaac Devine
12/27/2005 9:59:00 AM
Hi all,
I have been hacking away to create a simple library that adds
generics-like qualities to ruby. At the moment you can define methods
with type-matches, defaults if no match and different number of
argument matches. Currently thinking about adding pattern-matching
support from eigenclass blog. Depends on ruby facets.
Usage examples (Tests) are at the bottom.
Any suggestions, comments, flames ;-) ?
thanks,
Isaac Devine
P.S. BSD licensed. (don't know about the bit from why's guide though...)
--------------generics.rb--------------
# This file contains all you need to add generics to any ruby class :)
# ie.
# class Sample
# include Generics
# generic_method :name, Types do
# CODE
# end
## Default case
# generic_method :name do
# CODE
# end
# end
require 'mega/inheritor'
# This Object stuff from Why's Metaprogramming guide
class Object
# The hidden singleton lurks behind everyone
def metaclass; class << self; self; end; end
def meta_eval &blk; metaclass.instance_eval &blk; end
# Adds methods to a metaclass
def meta_def name, &blk
meta_eval { define_method name, &blk }
end
# Defines an instance method within a class
def class_def name, &blk
class_eval { define_method name, &blk }
end
end
# End Why's Stuff.
module Generics
class_inherit do
# Get a metaclass for this class
def metaclass; class << self; self; end; end
# metaprogramming code for generic_method
def generic_method(method_name, *types, &blk )
# have a class instance hash which holds the following:
# { :method_name => { type_name => block, type_name => block } }
# initialize it here...
class_eval {
# define the class generic_signatures if they don't exist
@generic_signatures = Hash.new if not
defined?(@generic_signatures) # define the generic method's signatures
if they don't exist @generic_signatures[method_name] = Hash.new unless
@generic_signatures.has_key?(method_name)
def self.generic_signatures
return @generic_signatures
end
}
# check to see if we are the default
if types.empty?
class_eval {
@generic_signatures[method_name].default = blk
}
else # got a typelist?
# create the type "string"
specific_method_name = types.join("_").to_sym
class_eval {
@generic_signatures[method_name][specific_method_name] = blk
}
end
# define the class method that does the dispatch to
# the appropiate block.
class_def(method_name) do |*args|
type_sig_arr = args.collect { |a| a.class }
type_sig = type_sig_arr.join('_').to_sym
self.class.generic_signatures[method_name][type_sig].call(*args)
end
end
end
end
class Test
include Generics
generic_method :get, String do |arg|
puts "In String... -- #{arg}"
end
generic_method :get, Fixnum do |arg|
puts "In Fixnum... -- #{arg}"
end
end
class Test2
include Generics
generic_method :two_args, String, Fixnum do |arg1, arg2|
puts "got a String and Fixnum"
end
generic_method :two_args, String, String do |arg1, arg2|
puts "got two Strings"
end
end
# does having a method that accepts two different numbers
# of arguments work?
class TestVariable
include Generics
generic_method :test_method, String do |arg1|
puts "single argument"
end
generic_method :test_method, String, Fixnum do |arg1,arg2|
puts "two arguments"
end
end
class TestDefault
include Generics
generic_method :test, String do |arg|
puts "in String!"
end
generic_method :test do |arg|
puts "The rest!"
end
end