diff --git a/lib/functor.rb b/lib/functor.rb index 893178b..0258cda 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -2,48 +2,96 @@ class Functor + module ClassMethods + + def instance_methods + super.reject { |method| method =~ /^functo_/ } + end + + def functors + @__functors ||= superclass.respond_to?( :functors ) ? + Functor::Method.copy_functors( superclass.functors ) : {} + end + + def functor( name, *pattern, &block ) + name = name.to_sym + f = ( functors[ name ] ||= Functor.new ) + f.register( pattern ) + define_method( "functo_#{pattern.hash}", block ) + unless respond_to?( name ) + define_method( name ) do | *args | + begin + signature = f.match( *args ) + send "functo_#{signature}", *args + rescue NoMethodError + raise ArgumentError.new( "No functor matches the given arguments for method #{name}." ) + end + end + end + end + + def functor_with_self( name, *pattern, &block ) + name = name.to_sym + f = ( functors[ name ] ||= Functor.new ) + f.register( pattern ) + define_method( "functo_#{pattern.hash}", block ) + unless respond_to?( name ) + define_method( name ) do | *args | + begin + signature = f.match( self, *args ) + send "functo_#{signature}", *args + rescue NoMethodError + raise ArgumentError.new( "No functor matches the given arguments for method #{name}." ) + end + end + end + end + end + module Method + def self.copy_functors( functors ) r = {} ; functors.each do | name, functor | r[ name ] = functor.clone end return r end + + def methods + super.reject { |method| method =~ /^functo_/ } + end + def self.included( k ) - def k.functors - @__functors ||= superclass.respond_to?( :functors ) ? - Functor::Method.copy_functors( superclass.functors ) : {} - end - def k.functor( name, *args, &block ) - name = name.to_sym - ( f = ( functors[ name ] or - ( functors[ name ] = Functor.new ) ) ).given( *args, &block ) - define_method( name ) { | *args | instance_exec( *args, &f.match( *args ) ) } - end - def k.functor_with_self( name, *args, &block ) - name = name.to_sym - ( f = ( functors[ name ] or - ( functors[ name ] = Functor.new ) ) ).given( *args, &block ) - define_method( name ) { | *args | instance_exec( *args, &f.match( self, *args ) ) } - end + k.extend ClassMethods end end def initialize( &block ) - @rules = [] ; yield( self ) if block_given? + @patterns = [] + @associations = {} + yield( self ) if block_given? end def initialize_copy( from ) - @rules = from.instance_eval { @rules.clone } + @patterns, @associations = from.instance_eval { [@patterns.clone, @associations.clone] } end def given( *pattern, &action ) - @rules << [ pattern, action ] + register pattern + name = "functo_#{pattern.hash}" + class << self; self; end.instance_eval do + define_method( name, action ) + end + end + + def register( pattern ) + @patterns.unshift pattern end def call( *args, &block ) - match( *args, &block ).call( *args ) + signature = match( *args, &block ) + send "functo_#{signature}", *args end def []( *args, &block ) @@ -53,20 +101,27 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def match( *args, &block ) - args << block if block_given? - pattern, action = @rules.reverse.find { | p, a | match?( args, p ) } - action or - raise ArgumentError.new( "Argument error: no functor matches the given arguments." ) + arg_sig = args.hash + if pattern_sig = @associations[arg_sig] + pattern_sig + else + args << block if block_given? + pattern = @patterns.find { | p | match?( args, p ) } + raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern + @associations[arg_sig] = pattern.hash + end end private def match?( args, pattern ) - args.zip( pattern ).all? { | arg, rule | pair?( arg, rule ) } if args.length == pattern.length + args.all? do |a| + p = pattern[args.index(a)]; p === a || ( p.respond_to?(:call) && p.call(a)) + end if args.length == pattern.length end - def pair?( arg, rule ) - ( rule.respond_to? :call and rule.call( arg ) ) or rule === arg + def pair?( arg, pat ) + ( pat.respond_to? :call and pat.call( arg ) ) or pat === arg end end \ No newline at end of file diff --git a/metrics/.gitignore b/metrics/.gitignore new file mode 100644 index 0000000..19c8276 --- /dev/null +++ b/metrics/.gitignore @@ -0,0 +1 @@ +stevedore diff --git a/metrics/benchmark.rb b/metrics/benchmark.rb new file mode 100644 index 0000000..2660b0b --- /dev/null +++ b/metrics/benchmark.rb @@ -0,0 +1,39 @@ +require 'rubygems' + +$:.unshift "#{here = File.dirname(__FILE__)}/stevedore/lib" +$:.unshift "#{here}/../lib" +require 'stevedore' +require 'functor' + + +class FuncFib < Steve + + subject "Fibonacci using Functor" + + # power 0.8 + # sig_level 0.05 + delta 0.01 + + before do + @fib ||= Functor.new do |f| + f.given( Integer ) { | n | f.call( n - 1 ) + f.call( n - 2 ) } + f.given( 0 ) { 0 } + f.given( 1 ) { 1 } + end + end +end + +fib_8 = FuncFib.new "f(8)" do + measure do + 64.times { @fib.call(8) } + end +end + +fib_16 = FuncFib.new "f(16)" do + measure do + 2.times { @fib.call(16) } + end +end + +# FuncFib.recommend_test_size( 8, 16) +FuncFib.compare_instances( 8, 128) \ No newline at end of file diff --git a/metrics/fib.rb b/metrics/fib.rb new file mode 100644 index 0000000..398a2c0 --- /dev/null +++ b/metrics/fib.rb @@ -0,0 +1,33 @@ +require "#{here = File.dirname(__FILE__)}/helpers" + +class FuncFib < Steve + + subject "Fibonacci using Functor" + + # power 0.8 + # sig_level 0.05 + delta 0.01 + + before do + @fib ||= Functor.new do |f| + f.given( Integer ) { | n | f.call( n - 1 ) + f.call( n - 2 ) } + f.given( 0 ) { 0 } + f.given( 1 ) { 1 } + end + end +end + +fib_8 = FuncFib.new "f(8)" do + measure do + 64.times { @fib.call(8) } + end +end + +fib_16 = FuncFib.new "f(16)" do + measure do + 2.times { @fib.call(16) } + end +end + +# FuncFib.recommend_test_size( 8, 16) +FuncFib.compare_instances( 8, 128) \ No newline at end of file diff --git a/metrics/helpers.rb b/metrics/helpers.rb new file mode 100644 index 0000000..fd8c4bc --- /dev/null +++ b/metrics/helpers.rb @@ -0,0 +1,6 @@ +require 'rubygems' + +$:.unshift "#{here = File.dirname(__FILE__)}/stevedore/lib" +$:.unshift "#{here}/../lib" +require 'stevedore' +require 'functor' \ No newline at end of file diff --git a/metrics/many_args.rb b/metrics/many_args.rb new file mode 100644 index 0000000..17bf2ca --- /dev/null +++ b/metrics/many_args.rb @@ -0,0 +1,71 @@ +require "#{here = File.dirname(__FILE__)}/helpers" + +class A + include Functor::Method + functor( :foo, 1, 2, 3, 4, 5, 6, 7 ) { |*x| "ints" } + functor( :foo, :a, :b, :c, :d, :e, :f, :g ) { |*x| "symbols" } + functor( :foo, *%w{ a b c d e f g } ) { |*x| "strings" } + functor( :foo, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 ) { |*x| "floats" } + functor( :foo, *Array.new(7, "one") ) { |*x| "ones" } +end + +class Native + def foo(*args) + case args + when [1, 2, 3, 4, 5, 6, 7] + "ints" + when [:a, :b, :c, :d, :e, :f, :g] + "symbols" + when %w{ a b c d e f g } + "strings" + when [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + "floats" + when Array.new(7, "one") + "ones" + else + raise ArgumentError + end + end +end + +class ManyArgs < Steve +end + +ManyArgs.new "native method" do + before do + @args = [ + [1, 2, 3, 4, 5, 6, 7], + [:a, :b, :c, :d, :e, :f, :g], + %w{ a b c d e f g }, + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], + Array.new(7, "one") + ] + @n = Native.new + end + measure do + 400.times do + @args.each { |args| @n.foo *args } + end + end +end + + +ManyArgs.new "functor method" do + before do + @args = [ + [1, 2, 3, 4, 5, 6, 7], + [:a, :b, :c, :d, :e, :f, :g], + %w{ a b c d e f g }, + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], + Array.new(7, "one") + ] + @a = A.new + end + measure do + 400.times do + @args.each { |args| @a.foo *args } + end + end +end + +ManyArgs.compare_instances( 16, 32) \ No newline at end of file diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb new file mode 100644 index 0000000..4268dd9 --- /dev/null +++ b/metrics/one_arg.rb @@ -0,0 +1,53 @@ +require "#{here = File.dirname(__FILE__)}/helpers" + +class A + include Functor::Method + functor( :foo, Integer ) { |x| :integer } + functor( :foo, String ) { |x| :string } + functor( :foo, Float ) { |x| :float } + functor( :foo, Symbol ) { |x| :symbol } + functor( :foo, "one" ) { |x| :one } +end + +class Native + def foo(x) + case x + when Integer then :integer + when String then :string + when Float then :float + when Symbol then :symbol + when "one" then :one + else + raise ArgumentError + end + end +end + +class OneArg < Steve + +end + +OneArg.new "native method" do + before do + @n = Native.new + end + measure do + 200.times do + [ 1, 2, 3, 1.0, 2.0, 3.0, "1", "2", "3", :uno, :dos, :tres, "one"].each { |item| @n.foo item } + end + end +end + +OneArg.new "functor method" do + before do + @a = A.new + end + measure do + 200.times do + [ 1, 2, 1.0, 2.0, "1", "2", :uno, :dos, "one"].each { |item| @a.foo item } + end + end +end + + +OneArg.compare_instances( 16, 32) \ No newline at end of file diff --git a/metrics/two_arg.rb b/metrics/two_arg.rb new file mode 100644 index 0000000..d33cf2d --- /dev/null +++ b/metrics/two_arg.rb @@ -0,0 +1,39 @@ +require "#{here = File.dirname(__FILE__)}/helpers" + +class A + include Functor::Method + functor( :foo, Integer, String ) { |x| :int_string } + functor( :foo, Integer, Float ) { |x| :int_float } + functor( :foo, String, Float ) { |x| :string_float } + functor( :foo, String, Symbol ) { |x| :string_symbol } + functor( :foo, Float, Symbol ) { |x| :float_symbol } + functor( :foo, Float, String ) { |x| :float_string } + functor( :foo, Symbol, "one" ) { |x| :symbol_one } + functor( :foo, "one", false ) { |x| :one_false } +end + +class B + def foo(x,y) + case x + when Integer + case y + when String + :int_string + when Float + :int_float + end + when String + case y + when Float + :string_float + when Symbol + :string_symbol + end + when Float + when Symbol + when "one" + else + raise ArgumentError + end + end +end \ No newline at end of file diff --git a/test/inheritance.rb b/test/inheritance.rb index 1ad2188..cba8102 100644 --- a/test/inheritance.rb +++ b/test/inheritance.rb @@ -21,4 +21,8 @@ class B < A B.new.foo( "bar" ).should == [ B, String ] end + # specify "by allowing #super" do + # B.new.foo(3.0).should == [ Float, A] + # end + end diff --git a/test/with_self.rb b/test/with_self.rb index f978818..8e7606f 100644 --- a/test/with_self.rb +++ b/test/with_self.rb @@ -6,6 +6,7 @@ class A def initialize( x ) ; @bar = x ; end functor_with_self( :foo, self, Integer ) { |x| x } functor_with_self( :foo, lambda{ |x| x.bar == true }, Integer ) { |s| 'bar' } + functor_with_self( :foo, lambda{ |x| x.bar.is_a? String }, Integer ) { |s| 'I be string' } end describe "Functor methods should support allow matching on self" do @@ -17,5 +18,9 @@ def initialize( x ) ; @bar = x ; end specify "or by simply providing self as an argument" do A.new( false ).foo( 5 ).should == 5 end + + specify "another guard example, for those who need it" do + A.new( "me" ).foo( 87 ).should == "I be string" + end end