From 1218a32c6de72e64358a98f230aefdde4c9aaacf Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 05:50:08 -0600 Subject: [PATCH 01/42] Functor::Method working with instadef; direct Functor.new notsomuch --- lib/functor.rb | 60 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 893178b..626148d 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -10,22 +10,46 @@ def self.copy_functors( functors ) return r 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 ) + + def k.functor( name, *pattern, &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 ) ) } + 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 k.functor_with_self( name, *args, &block ) + + def k.functor_with_self( name, *pattern, &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 ) ) } + 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 end @@ -38,12 +62,22 @@ def initialize_copy( from ) @rules = from.instance_eval { @rules.clone } end + def register( *pattern ) + @rules << pattern + end + def given( *pattern, &action ) - @rules << [ pattern, action ] + register pattern + signature = pattern.hash + name = "functo_#{signature}" + class << self; self; end.instance_eval do + define_method( name, action ) + end end def call( *args, &block ) - match( *args, &block ).call( *args ) + signature = match( *args, &block ) + send "functo_#{signature}", *args end def []( *args, &block ) @@ -54,9 +88,9 @@ 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." ) + pattern = @rules.reverse.find { | p | match?( args, p ) } + raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern + pattern.hash end private From 978b5a1d217439a0ec06a422f696783fddad52ba Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 06:05:08 -0600 Subject: [PATCH 02/42] instadef now working with standalone functors --- lib/functor.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 626148d..248126d 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -3,12 +3,14 @@ class Functor module Method + def self.copy_functors( functors ) r = {} ; functors.each do | name, functor | r[ name ] = functor.clone end return r end + def self.included( k ) def k.functors @@ -19,7 +21,7 @@ def k.functors def k.functor( name, *pattern, &block ) name = name.to_sym f = ( functors[ name ] ||= Functor.new ) - f.register( *pattern ) + f.register( pattern ) define_method( "functo_#{pattern.hash}", block ) unless respond_to?( name ) define_method( name ) do | *args | @@ -36,7 +38,7 @@ def k.functor( name, *pattern, &block ) def k.functor_with_self( name, *pattern, &block ) name = name.to_sym f = ( functors[ name ] ||= Functor.new ) - f.register( *pattern ) + f.register( pattern ) define_method( "functo_#{pattern.hash}", block ) unless respond_to?( name ) define_method( name ) do | *args | @@ -55,26 +57,27 @@ def k.functor_with_self( name, *pattern, &block ) 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 } - end - - def register( *pattern ) - @rules << pattern + @patterns, @associations = from.instance_eval { [@patterns.clone, @associations.clone] } end def given( *pattern, &action ) register pattern - signature = pattern.hash - name = "functo_#{signature}" + 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 ) signature = match( *args, &block ) send "functo_#{signature}", *args @@ -88,7 +91,7 @@ def to_proc ; lambda { |*args| self.call( *args ) } ; end def match( *args, &block ) args << block if block_given? - pattern = @rules.reverse.find { | p | match?( args, p ) } + pattern = @patterns.find { | p | match?( args, p ) } raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern pattern.hash end @@ -96,11 +99,11 @@ def match( *args, &block ) private def match?( args, pattern ) - args.zip( pattern ).all? { | arg, rule | pair?( arg, rule ) } if args.length == pattern.length + args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } 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 From c9c3a660e8a3cb93e6b45c11429ac1405b9052d2 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 06:24:08 -0600 Subject: [PATCH 03/42] extra with_self test for clarity --- test/with_self.rb | 5 +++++ 1 file changed, 5 insertions(+) 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 From 1dad347f6eabde5c03c760914de589b8f5234fb0 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 06:25:13 -0600 Subject: [PATCH 04/42] compilation via tracking of arg signatures passed to match --- lib/functor.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 248126d..edd00c9 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -58,7 +58,7 @@ def k.functor_with_self( name, *pattern, &block ) def initialize( &block ) @patterns = [] - @associations = [] + @associations = {} yield( self ) if block_given? end @@ -90,10 +90,15 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def match( *args, &block ) - args << block if block_given? - pattern = @patterns.find { | p | match?( args, p ) } - raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern - pattern.hash + 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 From 7ccd38d5a7da6b4e2e3f7910052892133d11a146 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 06:25:36 -0600 Subject: [PATCH 05/42] set up metrics --- metrics/benchmark.rb | 26 ++++++++++++++++++++++++++ metrics/stevedore | 1 + 2 files changed, 27 insertions(+) create mode 100644 metrics/benchmark.rb create mode 120000 metrics/stevedore diff --git a/metrics/benchmark.rb b/metrics/benchmark.rb new file mode 100644 index 0000000..8e00136 --- /dev/null +++ b/metrics/benchmark.rb @@ -0,0 +1,26 @@ +require 'rubygems' + +$:.unshift "stevedore/lib" +$:.unshift "#{File.dirname(__FILE__)}/../lib" +require 'stevedore' +require 'functor' + +commit = `git show-ref -s --abbrev HEAD`.chomp + +bm = Steve.new "Functor at #{commit}" do + + 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 + + measure do + [*0..15].map( &@fib ) + end + +end + +Steve.compare_instances( 10, 10) \ No newline at end of file diff --git a/metrics/stevedore b/metrics/stevedore new file mode 120000 index 0000000..7acc4f3 --- /dev/null +++ b/metrics/stevedore @@ -0,0 +1 @@ +/Users/matthew/dev/src/stevedore \ No newline at end of file From 368f63ae93fd9ae17ca7805db60672fbd62af72e Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 06:55:08 -0600 Subject: [PATCH 06/42] updated benchmark --- metrics/benchmark.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/metrics/benchmark.rb b/metrics/benchmark.rb index 8e00136..6be0318 100644 --- a/metrics/benchmark.rb +++ b/metrics/benchmark.rb @@ -1,14 +1,14 @@ require 'rubygems' -$:.unshift "stevedore/lib" -$:.unshift "#{File.dirname(__FILE__)}/../lib" +$:.unshift "#{here = File.dirname(__FILE__)}/stevedore/lib" +$:.unshift "#{here}/../lib" require 'stevedore' require 'functor' -commit = `git show-ref -s --abbrev HEAD`.chomp -bm = Steve.new "Functor at #{commit}" do +class FuncFib < Steve + subject "Fibonacci using Functor" before do @fib ||= Functor.new do |f| f.given( Integer ) { | n | f.call( n - 1 ) + f.call( n - 2 ) } @@ -16,11 +16,16 @@ f.given( 1 ) { 1 } end end - +end + +fib_13 = FuncFib.new "f(10)" do measure do - [*0..15].map( &@fib ) + 100.times { @fib.call(10) } end - end -Steve.compare_instances( 10, 10) \ No newline at end of file +fib_15 = FuncFib.new "f(20)" do + measure { @fib.call(20) } +end + +FuncFib.compare_instances( 4, 8) \ No newline at end of file From 956c9f213fbe999d56ea167cd24fec1c7d41ebd0 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 07:42:43 -0600 Subject: [PATCH 07/42] twiddle benchmark for times --- metrics/benchmark.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/metrics/benchmark.rb b/metrics/benchmark.rb index 6be0318..2660b0b 100644 --- a/metrics/benchmark.rb +++ b/metrics/benchmark.rb @@ -9,6 +9,11 @@ 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 ) } @@ -18,14 +23,17 @@ class FuncFib < Steve end end -fib_13 = FuncFib.new "f(10)" do +fib_8 = FuncFib.new "f(8)" do measure do - 100.times { @fib.call(10) } + 64.times { @fib.call(8) } end end -fib_15 = FuncFib.new "f(20)" do - measure { @fib.call(20) } +fib_16 = FuncFib.new "f(16)" do + measure do + 2.times { @fib.call(16) } + end end -FuncFib.compare_instances( 4, 8) \ No newline at end of file +# FuncFib.recommend_test_size( 8, 16) +FuncFib.compare_instances( 8, 128) \ No newline at end of file From ccdf23c8798f6a992c1319a4c69fb0d64181ddf7 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 11:21:12 -0600 Subject: [PATCH 08/42] symbolize method name in exception --- lib/functor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index edd00c9..6b85f1e 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -29,7 +29,7 @@ def k.functor( name, *pattern, &block ) signature = f.match( *args ) send "functo_#{signature}", *args rescue NoMethodError - raise ArgumentError.new( "No functor matches the given arguments for method #{name}." ) + raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end end end @@ -46,7 +46,7 @@ def k.functor_with_self( name, *pattern, &block ) signature = f.match( self, *args ) send "functo_#{signature}", *args rescue NoMethodError - raise ArgumentError.new( "No functor matches the given arguments for method #{name}." ) + raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end end end From 9768983d6ed1352d3e488e36db81cda861de54af Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 9 Jan 2009 12:14:42 -0600 Subject: [PATCH 09/42] hide functo_methods in functor object --- lib/functor.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 6b85f1e..be826e8 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -1,4 +1,6 @@ require "#{File.dirname(__FILE__)}/object" +require 'rubygems' +require 'metaid' class Functor @@ -22,12 +24,14 @@ def k.functor( name, *pattern, &block ) name = name.to_sym f = ( functors[ name ] ||= Functor.new ) f.register( pattern ) - define_method( "functo_#{pattern.hash}", block ) + f.meta_def "functo_#{pattern.hash}" do |caller, *args| + caller.instance_exec *args, &block + end unless respond_to?( name ) define_method( name ) do | *args | begin signature = f.match( *args ) - send "functo_#{signature}", *args + f.send "functo_#{signature}", self, *args rescue NoMethodError raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end @@ -39,12 +43,14 @@ def k.functor_with_self( name, *pattern, &block ) name = name.to_sym f = ( functors[ name ] ||= Functor.new ) f.register( pattern ) - define_method( "functo_#{pattern.hash}", block ) + f.meta_def "functo_#{pattern.hash}" do |caller, *args| + caller.instance_exec *args, &block + end unless respond_to?( name ) define_method( name ) do | *args | begin signature = f.match( self, *args ) - send "functo_#{signature}", *args + f.send "functo_#{signature}", self, *args rescue NoMethodError raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end From 700d6e29ae889fd6616142ca2b021e2888029c53 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 15:36:57 -0600 Subject: [PATCH 10/42] y'all don't need my symlinks --- metrics/.gitignore | 1 + metrics/stevedore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 metrics/.gitignore delete mode 120000 metrics/stevedore 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/stevedore b/metrics/stevedore deleted file mode 120000 index 7acc4f3..0000000 --- a/metrics/stevedore +++ /dev/null @@ -1 +0,0 @@ -/Users/matthew/dev/src/stevedore \ No newline at end of file From 585ea6189be3e8ecfde232dfcdc4b17e143a7a47 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 15:47:52 -0600 Subject: [PATCH 11/42] splitting up bms --- metrics/fib.rb | 33 +++++++++++++++++++++++++++++ metrics/helpers.rb | 6 ++++++ metrics/one_arg.rb | 52 ++++++++++++++++++++++++++++++++++++++++++++++ metrics/two_arg.rb | 24 +++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 metrics/fib.rb create mode 100644 metrics/helpers.rb create mode 100644 metrics/one_arg.rb create mode 100644 metrics/two_arg.rb 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/one_arg.rb b/metrics/one_arg.rb new file mode 100644 index 0000000..09c63c9 --- /dev/null +++ b/metrics/one_arg.rb @@ -0,0 +1,52 @@ +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 B + 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 "functor method" do + before do + @a = A.new + end + measure do + 100.times do + [ 1, 2, 3, 1.0, 2.0, 3.0, "1", "2", "3", :uno, :dos, :tres, "one"].each { |item| @a.foo item } + end + end +end + +OneArg.new "native method" do + before do + @b = B.new + end + measure do + 100.times do + [ 1, 2, 3, 1.0, 2.0, 3.0, "1", "2", "3", :uno, :dos, :tres, "one"].each { |item| @b.foo item } + end + end +end + +OneArg.compare_instances( 2, 6) \ No newline at end of file diff --git a/metrics/two_arg.rb b/metrics/two_arg.rb new file mode 100644 index 0000000..cf2d437 --- /dev/null +++ b/metrics/two_arg.rb @@ -0,0 +1,24 @@ +require "#{here = File.dirname(__FILE__)}/helpers" + +class A + include Functor::Method + functor( :foo, Integer, String ) { |x| :integer } + functor( :foo, String, Float ) { |x| :string } + functor( :foo, Float, Symbol ) { |x| :float } + functor( :foo, Symbol, "one" ) { |x| :symbol } + functor( :foo, "one", false ) { |x| :one } +end + +class B + def foo(x) + case x + when Integer + when String + when Float + when Symbol + when "one" + else + raise ArgumentError + end + end +end \ No newline at end of file From 474171940c6eb051dd99a3540a1aa19beeea8d40 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 17:31:03 -0600 Subject: [PATCH 12/42] working chain defs for functor and with_self --- lib/functor.rb | 80 +++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index be826e8..d0530e0 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -20,40 +20,34 @@ def k.functors Functor::Method.copy_functors( superclass.functors ) : {} end - def k.functor( name, *pattern, &block ) - name = name.to_sym - f = ( functors[ name ] ||= Functor.new ) - f.register( pattern ) - f.meta_def "functo_#{pattern.hash}" do |caller, *args| - caller.instance_exec *args, &block - end - unless respond_to?( name ) - define_method( name ) do | *args | - begin - signature = f.match( *args ) - f.send "functo_#{signature}", self, *args - rescue NoMethodError - raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) - end - end - end + def k.functor( name, *pattern, &action ) + name = name.to_s + old = instance_method(name) if instance_methods.include?( name ) + define_method( name, action ) + newest = instance_method(name) + define_method( name ) do | *args | + if Functor.match?(args, pattern) + newest.bind(self).call(*args) + elsif old + old.bind(self).call(*args) + else + raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) + end + end end - def k.functor_with_self( name, *pattern, &block ) - name = name.to_sym - f = ( functors[ name ] ||= Functor.new ) - f.register( pattern ) - f.meta_def "functo_#{pattern.hash}" do |caller, *args| - caller.instance_exec *args, &block - end - unless respond_to?( name ) - define_method( name ) do | *args | - begin - signature = f.match( self, *args ) - f.send "functo_#{signature}", self, *args - rescue NoMethodError - raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) - end + def k.functor_with_self( name, *pattern, &action ) + name = name.to_s + old = instance_method(name) if instance_methods.include?( name ) + define_method( name, action ) + newest = instance_method(name) + define_method( name ) do | *args | + if Functor.match?([self] + args, pattern) + newest.bind(self).call(*args) + elsif old + old.bind(self).call(*args) + else + raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end end end @@ -64,7 +58,6 @@ def k.functor_with_self( name, *pattern, &block ) def initialize( &block ) @patterns = [] - @associations = {} yield( self ) if block_given? end @@ -96,18 +89,19 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def match( *args, &block ) - 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 + args << block if block_given? + pattern = @patterns.find { | p | match?( args, p ) } + raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern end - private + + def self.match?( args, pattern ) + args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } if args.length == pattern.length + end + + def self.pair?( arg, pat ) + ( pat.respond_to? :call and pat.call( arg ) ) or pat === arg + end def match?( args, pattern ) args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } if args.length == pattern.length From bc396aeb23fb7fbcf5b5fad18c9bd7802df653cc Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 17:42:56 -0600 Subject: [PATCH 13/42] all tests pass with chained defs; major cleanup --- doc/README | 4 --- lib/functor.rb | 59 ++++++++++++--------------------------------- test/functor.rb | 6 ++--- test/inheritance.rb | 5 ++++ 4 files changed, 24 insertions(+), 50 deletions(-) diff --git a/doc/README b/doc/README index e5e5ef9..76793c0 100644 --- a/doc/README +++ b/doc/README @@ -17,10 +17,6 @@ To use it in a class: r.repeat( "-" ) # => "- - - - -" r.repeat( 7.3 ) # => ArgumentError! -Warning: This defines a class instance variable @__functors behind the scenes as a side-effect. Also, although inheritance works within a functor method, super does not. To call the parent method, you need to call it explicitly using the #functors class method, like this: - - A.functors[ :foo ].apply( self, 'bar' ) - = Stand-Alone Functors You can also define Functor objects directly: diff --git a/lib/functor.rb b/lib/functor.rb index d0530e0..a47550c 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -6,20 +6,8 @@ class Functor module Method - def self.copy_functors( functors ) - r = {} ; functors.each do | name, functor | - r[ name ] = functor.clone - end - return r - end - def self.included( k ) - def k.functors - @__functors ||= superclass.respond_to?( :functors ) ? - Functor::Method.copy_functors( superclass.functors ) : {} - end - def k.functor( name, *pattern, &action ) name = name.to_s old = instance_method(name) if instance_methods.include?( name ) @@ -57,29 +45,29 @@ def k.functor_with_self( name, *pattern, &action ) def initialize( &block ) - @patterns = [] yield( self ) if block_given? end - def initialize_copy( from ) - @patterns, @associations = from.instance_eval { [@patterns.clone, @associations.clone] } - end - def given( *pattern, &action ) - register pattern - name = "functo_#{pattern.hash}" + name = "call" + old = method(name) if methods.include?( name ) class << self; self; end.instance_eval do define_method( name, action ) end - end - - def register( pattern ) - @patterns.unshift pattern - end - - def call( *args, &block ) - signature = match( *args, &block ) - send "functo_#{signature}", *args + newest = method(name) + class << self; self; end.instance_eval do + + define_method( name ) do | *args | + if Functor.match?(args, pattern) + newest.call(*args) + elsif old + old.call(*args) + else + raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) + end + end + + end end def []( *args, &block ) @@ -88,13 +76,6 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end - def match( *args, &block ) - args << block if block_given? - pattern = @patterns.find { | p | match?( args, p ) } - raise ArgumentError.new( "No functor matches the given arguments." ) unless pattern - end - - def self.match?( args, pattern ) args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } if args.length == pattern.length end @@ -102,13 +83,5 @@ def self.match?( args, pattern ) def self.pair?( arg, pat ) ( pat.respond_to? :call and pat.call( arg ) ) or pat === arg end - - def match?( args, pattern ) - args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } if args.length == pattern.length - end - - 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/test/functor.rb b/test/functor.rb index a5d78a2..1c7815f 100644 --- a/test/functor.rb +++ b/test/functor.rb @@ -12,12 +12,12 @@ class Repeater before do @r = Repeater.new - @r.times = 5 + @r.times = 3 end specify "invoke different methods with object scope based on arguments" do - @r.repeat( 5 ).should == 25 - @r.repeat( "-" ).should == '- - - - -' + @r.repeat( 5 ).should == 15 + @r.repeat( "-" ).should == '- - -' @r.repeat.should == nil end diff --git a/test/inheritance.rb b/test/inheritance.rb index 1ad2188..d442ea1 100644 --- a/test/inheritance.rb +++ b/test/inheritance.rb @@ -9,6 +9,7 @@ class A class B < A functor( :foo, String ) { |s| [ B, String ] } + functor( :foo, Float ) { |x| super.reverse } end describe "Functor methods should support inheritance" do @@ -21,4 +22,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 From 3e61cca3a5234db785aca7fba1d9fb9dca271427 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 17:45:08 -0600 Subject: [PATCH 14/42] more runs for one arg bm --- metrics/one_arg.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index 09c63c9..aaa04b2 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -49,4 +49,4 @@ class OneArg < Steve end end -OneArg.compare_instances( 2, 6) \ No newline at end of file +OneArg.compare_instances( 10, 50) \ No newline at end of file From c7b30023de20e5e549fb5f8231dbe5496c492942 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 13 Jan 2009 17:45:37 -0600 Subject: [PATCH 15/42] fiddling with two arg bm; not ready yet --- metrics/two_arg.rb | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/metrics/two_arg.rb b/metrics/two_arg.rb index cf2d437..d33cf2d 100644 --- a/metrics/two_arg.rb +++ b/metrics/two_arg.rb @@ -2,18 +2,33 @@ class A include Functor::Method - functor( :foo, Integer, String ) { |x| :integer } - functor( :foo, String, Float ) { |x| :string } - functor( :foo, Float, Symbol ) { |x| :float } - functor( :foo, Symbol, "one" ) { |x| :symbol } - functor( :foo, "one", false ) { |x| :one } + 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) + 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" From 5234ce961e5a1d99588455251c875a4a58ffece0 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 14 Jan 2009 11:15:06 -0600 Subject: [PATCH 16/42] improved match? --- lib/functor.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index a47550c..0e01978 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -77,11 +77,9 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def self.match?( args, pattern ) - args.zip( pattern ).all? { | arg, pat | pair?( arg, pat ) } if args.length == pattern.length - end - - def self.pair?( arg, pat ) - ( pat.respond_to? :call and pat.call( arg ) ) or pat === arg + 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 end \ No newline at end of file From ac973329f05f9e44d4de01ef200bd0b7d5e10ad3 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 14 Jan 2009 11:34:27 -0600 Subject: [PATCH 17/42] many_args and one_args bms --- metrics/many_args.rb | 71 ++++++++++++++++++++++++++++++++++++++++++++ metrics/one_arg.rb | 21 ++++++------- 2 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 metrics/many_args.rb 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 index aaa04b2..4268dd9 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -9,7 +9,7 @@ class A functor( :foo, "one" ) { |x| :one } end -class B +class Native def foo(x) case x when Integer then :integer @@ -27,26 +27,27 @@ class OneArg < Steve end -OneArg.new "functor method" do +OneArg.new "native method" do before do - @a = A.new + @n = Native.new end measure do - 100.times do - [ 1, 2, 3, 1.0, 2.0, 3.0, "1", "2", "3", :uno, :dos, :tres, "one"].each { |item| @a.foo item } + 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 "native method" do +OneArg.new "functor method" do before do - @b = B.new + @a = A.new end measure do - 100.times do - [ 1, 2, 3, 1.0, 2.0, 3.0, "1", "2", "3", :uno, :dos, :tres, "one"].each { |item| @b.foo item } + 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( 10, 50) \ No newline at end of file + +OneArg.compare_instances( 16, 32) \ No newline at end of file From 3cb9f2b26c83c35d02d5f44ffb516bd4a447df0b Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 14 Jan 2009 13:23:42 -0600 Subject: [PATCH 18/42] cache successfully matched args --- lib/functor.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/functor.rb b/lib/functor.rb index 0e01978..02ed43f 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -77,7 +77,10 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def self.match?( args, pattern ) - args.all? do |a| + @cache ||= {} + a_hash, p_hash = args.hash, pattern.hash + return true if @cache[a_hash] == p_hash + @cache[a_hash] = p_hash if 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 From bea9e8537225927e63ecb0486dccfa07603a2a6c Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 14 Jan 2009 13:41:43 -0600 Subject: [PATCH 19/42] short circuit to stored method if found in args hash --- lib/functor.rb | 12 +++++++----- test/inheritance.rb | 8 ++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 02ed43f..a9c0915 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -14,7 +14,12 @@ def k.functor( name, *pattern, &action ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | - if Functor.match?(args, pattern) + @cache ||= {} + args_hash = args.hash + if meth = @cache[args_hash] + meth.bind(self).call(*args) + elsif Functor.match?(args, pattern) + @cache[args_hash] = newest newest.bind(self).call(*args) elsif old old.bind(self).call(*args) @@ -77,10 +82,7 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def self.match?( args, pattern ) - @cache ||= {} - a_hash, p_hash = args.hash, pattern.hash - return true if @cache[a_hash] == p_hash - @cache[a_hash] = p_hash if args.all? do |a| + 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 diff --git a/test/inheritance.rb b/test/inheritance.rb index d442ea1..dae6624 100644 --- a/test/inheritance.rb +++ b/test/inheritance.rb @@ -9,7 +9,7 @@ class A class B < A functor( :foo, String ) { |s| [ B, String ] } - functor( :foo, Float ) { |x| super.reverse } + # functor( :foo, Float ) { |x| super.reverse } end describe "Functor methods should support inheritance" do @@ -22,8 +22,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 + # specify "by allowing #super" do + # B.new.foo(3.0).should == [ Float, A] + # end end From caa3fb01a8dd21be9c597a60cb80e308574a698c Mon Sep 17 00:00:00 2001 From: Matthew King Date: Thu, 15 Jan 2009 17:49:09 -0600 Subject: [PATCH 20/42] functor cache is per-method (duh) + test to prove + better BM --- lib/functor.rb | 25 +++++++++++++++++++++---- metrics/one_arg.rb | 21 +++++++++++++-------- test/functor.rb | 2 ++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index a9c0915..d85acdb 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -6,20 +6,37 @@ class Functor module Method + def functor_cache + self.class.functor_cache + end + def self.included( k ) + def k.functor_cache + @functor_cache ||= {} + end + + def k.functor_cache_size + @functor_cache_size + end + + def k.functor_cache_size=(val) + @functor_cache_size = val + end + def k.functor( name, *pattern, &action ) name = name.to_s old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | - @cache ||= {} - args_hash = args.hash - if meth = @cache[args_hash] + signature = args.hash + cache = (self.class.functor_cache[name] ||= {}) + cache.clear if c_size = self.class.functor_cache_size && cache.size > c_size + if meth = cache[signature] meth.bind(self).call(*args) elsif Functor.match?(args, pattern) - @cache[args_hash] = newest + cache[signature] = newest newest.bind(self).call(*args) elsif old old.bind(self).call(*args) diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index 4268dd9..6cd9a95 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -2,6 +2,7 @@ class A include Functor::Method + functor( :foo, Integer ) { |x| :integer } functor( :foo, String ) { |x| :string } functor( :foo, Float ) { |x| :float } @@ -24,30 +25,34 @@ def foo(x) end class OneArg < Steve - + before do + nums = (1..100).to_a + alphas = ("a".."cv").to_a + @args = nums + nums.map { |i| i.to_f } + alphas + alphas.map { |i| i.to_sym } + Array.new(100, "one") + end end OneArg.new "native method" do - before do + before_sample 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 } + 10.times do + @args.each { |item| @n.foo item } end end end OneArg.new "functor method" do - before do + before_sample 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 } + 10.times do + @args.each { |item| @a.foo item } end end end -OneArg.compare_instances( 16, 32) \ No newline at end of file +OneArg.compare_instances( 8, 64) \ No newline at end of file diff --git a/test/functor.rb b/test/functor.rb index 1c7815f..dcb4899 100644 --- a/test/functor.rb +++ b/test/functor.rb @@ -6,6 +6,7 @@ class Repeater functor( :repeat, Integer ) { |x| x * @times } functor( :repeat, String ) { |s| [].fill( s, 0, @times ).join(' ') } functor( :repeat ) { nil } + functor( :distraction, Integer ) { |x| "Boo!" } end describe "Dispatch on instance method should" do @@ -16,6 +17,7 @@ class Repeater end specify "invoke different methods with object scope based on arguments" do + @r.distraction( 5 ) @r.repeat( 5 ).should == 15 @r.repeat( "-" ).should == '- - -' @r.repeat.should == nil From 7f31a64a10f2b9e55d02072e0467b5a05bd90489 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 16:08:15 -0600 Subject: [PATCH 21/42] magic of closures helps caching become marvelous --- lib/functor.rb | 23 ++++++++++++++--------- metrics/many_args.rb | 2 +- metrics/one_arg.rb | 16 ++++++++-------- test/inheritance.rb | 24 ++++++++++++------------ test/matchers.rb | 8 ++++---- test/reopening.rb | 6 +++--- test/supplement.rb | 20 ++++++++++++++++++++ test/with_self.rb | 8 ++++---- 8 files changed, 66 insertions(+), 41 deletions(-) create mode 100644 test/supplement.rb diff --git a/lib/functor.rb b/lib/functor.rb index d85acdb..ac1aba9 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -16,26 +16,23 @@ def k.functor_cache @functor_cache ||= {} end - def k.functor_cache_size - @functor_cache_size - end - - def k.functor_cache_size=(val) - @functor_cache_size = val + def k.functor_cache_size(val=nil) + @functor_cache_size = val if val end def k.functor( name, *pattern, &action ) name = name.to_s + cache = (functor_cache[name] ||= {}) + cache_size = functor_cache_size old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | signature = args.hash - cache = (self.class.functor_cache[name] ||= {}) - cache.clear if c_size = self.class.functor_cache_size && cache.size > c_size if meth = cache[signature] meth.bind(self).call(*args) elsif Functor.match?(args, pattern) + cache = {} if cache_size && cache.size > cache_size cache[signature] = newest newest.bind(self).call(*args) elsif old @@ -48,11 +45,19 @@ def k.functor( name, *pattern, &action ) def k.functor_with_self( name, *pattern, &action ) name = name.to_s + cache = (functor_cache[name] ||= {}) + cache_size = functor_cache_size old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | - if Functor.match?([self] + args, pattern) + s_args = [self] + args + signature = (s_args).hash + if meth = cache[signature] + meth.bind(self).call(*args) + elsif Functor.match?(s_args, pattern) + cache = {} if cache_size && cache.size > cache_size + cache[signature] = newest newest.bind(self).call(*args) elsif old old.bind(self).call(*args) diff --git a/metrics/many_args.rb b/metrics/many_args.rb index 17bf2ca..ff5fb8c 100644 --- a/metrics/many_args.rb +++ b/metrics/many_args.rb @@ -68,4 +68,4 @@ class ManyArgs < Steve end end -ManyArgs.compare_instances( 16, 32) \ No newline at end of file +ManyArgs.compare_instances( 4, 16) \ No newline at end of file diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index 6cd9a95..a923e96 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -2,6 +2,7 @@ class A include Functor::Method + functor_cache_size 1000 functor( :foo, Integer ) { |x| :integer } functor( :foo, String ) { |x| :string } @@ -32,27 +33,26 @@ class OneArg < Steve end end -OneArg.new "native method" do +OneArg.new "functor method" do before_sample do - @n = Native.new + @a = A.new end measure do 10.times do - @args.each { |item| @n.foo item } + @args.each { |item| @a.foo item } end end end -OneArg.new "functor method" do +OneArg.new "native method" do before_sample do - @a = A.new + @n = Native.new end measure do 10.times do - @args.each { |item| @a.foo item } + @args.each { |item| @n.foo item } end end end - -OneArg.compare_instances( 8, 64) \ No newline at end of file +OneArg.compare_instances( 4, 8) \ No newline at end of file diff --git a/test/inheritance.rb b/test/inheritance.rb index dae6624..45a5e28 100644 --- a/test/inheritance.rb +++ b/test/inheritance.rb @@ -1,29 +1,29 @@ require "#{File.dirname(__FILE__)}/helpers" -class A +class Parent include Functor::Method - functor( :foo, Integer ) { |x| [ A, Integer ] } - functor( :foo, String ) { |s| [ A, String ] } - functor( :foo, Float ) { |h| [ A, Float ] } + functor( :foo, Integer ) { |x| [ Parent, Integer ] } + functor( :foo, String ) { |s| [ Parent, String ] } + functor( :foo, Float ) { |h| [ Parent, Float ] } end -class B < A - functor( :foo, String ) { |s| [ B, String ] } - # functor( :foo, Float ) { |x| super.reverse } +class Child < Parent + functor( :foo, String ) { |s| [ Child, String ] } + functor( :foo, Float ) { |x| super.reverse } end describe "Functor methods should support inheritance" do specify "by inheriting base class implementations" do - B.new.foo( 5 ).should == [ A, Integer ] + Child.new.foo( 5 ).should == [ Parent, Integer ] end specify "by allowing derived classes to override an implementation" do - B.new.foo( "bar" ).should == [ B, String ] + Child.new.foo( "bar" ).should == [ Child, String ] end - # specify "by allowing #super" do - # B.new.foo(3.0).should == [ Float, A] - # end + specify "by allowing #super" do + Child.new.foo(3.0).should == [ Float, Parent] + end end diff --git a/test/matchers.rb b/test/matchers.rb index 0f92b9a..70bdf38 100644 --- a/test/matchers.rb +++ b/test/matchers.rb @@ -1,6 +1,6 @@ require "#{File.dirname(__FILE__)}/helpers" -class C +class Matchers include Functor::Method functor( :foo, Integer ) { |a| "===" } functor( :foo, 1 ) { |a| "==" } @@ -10,15 +10,15 @@ class C describe "Functors match" do specify "using ==" do - C.new.foo( 1 ).should == "==" + Matchers.new.foo( 1 ).should == "==" end specify "using ===" do - C.new.foo( 2 ).should == "===" + Matchers.new.foo( 2 ).should == "===" end specify "using #call" do - C.new.foo( "boo" ).should == "Lambda: boo" + Matchers.new.foo( "boo" ).should == "Lambda: boo" end end diff --git a/test/reopening.rb b/test/reopening.rb index f26688b..7a2094f 100644 --- a/test/reopening.rb +++ b/test/reopening.rb @@ -1,18 +1,18 @@ require "#{File.dirname(__FILE__)}/helpers" -class A +class Reopening include Functor::Method functor( :foo, Integer ) { |x| 1 } end -class A +class Reopening functor( :foo, Integer ) { |x| 2 } end describe "Functor methods should support reopening" do specify "by allowing reopening of a class to override an implementation" do - A.new.foo( 5 ).should == 2 + Reopening.new.foo( 5 ).should == 2 end end diff --git a/test/supplement.rb b/test/supplement.rb new file mode 100644 index 0000000..b2465d9 --- /dev/null +++ b/test/supplement.rb @@ -0,0 +1,20 @@ +require "#{File.dirname(__FILE__)}/helpers" + +class Additive + include Functor::Method + + def foo(*args) + args.reverse + end + + functor( :foo, Integer ) { |x| 1 } +end + +describe "A Functor method" do + + specify "supplements, rather than obliterating, an existing method" do + Additive.new.foo( 5 ).should == 1 + Additive.new.foo( :a, :b).should == [ :b, :a ] + end + +end \ No newline at end of file diff --git a/test/with_self.rb b/test/with_self.rb index 8e7606f..2e06cf7 100644 --- a/test/with_self.rb +++ b/test/with_self.rb @@ -1,6 +1,6 @@ require "#{File.dirname(__FILE__)}/helpers" -class A +class WithSelf attr_accessor :bar include Functor::Method def initialize( x ) ; @bar = x ; end @@ -12,15 +12,15 @@ def initialize( x ) ; @bar = x ; end describe "Functor methods should support allow matching on self" do specify "by allowing functor_with_self to provide a guard on self" do - A.new( true ).foo( 5 ).should == 'bar' + WithSelf.new( true ).foo( 5 ).should == 'bar' end specify "or by simply providing self as an argument" do - A.new( false ).foo( 5 ).should == 5 + WithSelf.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" + WithSelf.new( "me" ).foo( 87 ).should == "I be string" end end From 7957663d9da069d1e733fce357a146bc18a8479a Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 16:01:59 -0600 Subject: [PATCH 22/42] tiered caches based on exponential increasing count --- lib/functor.rb | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index ac1aba9..9b1a612 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -20,20 +20,52 @@ def k.functor_cache_size(val=nil) @functor_cache_size = val if val end + def k.functor_cache_base(val=nil) + if val + raise ArgumentError, "I need an Integer" unless val.is_a? Integer + @functor_cache_base = val + else + @functor_cache_base ||= 10 + end + end + + def k.functor_cache_1 + @functor_cache_1 ||= {} + end + + def k.functor_cache_2 + @functor_cache_2 ||= {} + end + + def k.functor_cache_3 + @functor_cache_3 ||= {} + end + def k.functor( name, *pattern, &action ) name = name.to_s - cache = (functor_cache[name] ||= {}) + cache_1, cache_2, cache_3 = (functor_cache_1[name] ||= {}), (functor_cache_2[name] ||= {}), (functor_cache_3[name] ||= {}) cache_size = functor_cache_size old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | signature = args.hash - if meth = cache[signature] + if meth = cache_3[signature] meth.bind(self).call(*args) + elsif meth = cache_2[signature] + cache_3 = {} if cache_size && cache_3.size > cache_size + count = meth.last + cache_3[signature] if count > functor_cache_base ** 2 + count += 1 + elsif meth = cache_1[signature] + cache_2 = {} if cache_size && cache_2.size > cache_size + count = meth.last + cache_2[signature] = meth if count > functor_cache_base + count += 1 + meth.first.bind(self).call(*args) elsif Functor.match?(args, pattern) - cache = {} if cache_size && cache.size > cache_size - cache[signature] = newest + cache_1 = {} if cache_size && cache_1.size > cache_size + cache_1[signature] = [newest, 0] newest.bind(self).call(*args) elsif old old.bind(self).call(*args) From d101c7bd10f3c8a5f9b6d057ffcdf11731647587 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 17:59:37 -0600 Subject: [PATCH 23/42] 4-tiers --- lib/functor.rb | 107 ++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 9b1a612..e6003bf 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -12,60 +12,71 @@ def functor_cache def self.included( k ) - def k.functor_cache - @functor_cache ||= {} + def k.functor_cache; @functor_cache ||= [{},{},{},{}]; end + def k.clear_functor_cache; @functor_cache = [{},{},{},{}]; end + + def k.functor_cache_config(options={}) + @functor_cache_size = options[:size] if options[:size] + @functor_cache_base = options[:base] if options[:base] end def k.functor_cache_size(val=nil) - @functor_cache_size = val if val + @functor_cache_size ||= val end + def k.functor_cache_base(val=nil) - if val - raise ArgumentError, "I need an Integer" unless val.is_a? Integer - @functor_cache_base = val - else - @functor_cache_base ||= 10 - end + @functor_cache_base || @functor_cache_base = ( val || 16 ) end - def k.functor_cache_1 - @functor_cache_1 ||= {} - end + def k.functor_cache_0; functor_cache[0]; end + def k.functor_cache_1; functor_cache[1]; end + def k.functor_cache_2; functor_cache[2]; end + def k.functor_cache_3; functor_cache[3]; end - def k.functor_cache_2 - @functor_cache_2 ||= {} + def k.functor( name, *pattern, &action ) + _functor( name, false, *pattern, &action) end - def k.functor_cache_3 - @functor_cache_3 ||= {} + def k.functor_with_self( name, *pattern, &action ) + _functor( name, true, *pattern, &action) end - def k.functor( name, *pattern, &action ) + private + + def k._functor( name, with_self=false, *pattern, &action) name = name.to_s - cache_1, cache_2, cache_3 = (functor_cache_1[name] ||= {}), (functor_cache_2[name] ||= {}), (functor_cache_3[name] ||= {}) - cache_size = functor_cache_size + c0,c1,c2,c3 = (0..3).map { |i| functor_cache[i][name] ||= {} } + cache_size, cache_base = functor_cache_size, functor_cache_base old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) define_method( name ) do | *args | - signature = args.hash - if meth = cache_3[signature] - meth.bind(self).call(*args) - elsif meth = cache_2[signature] - cache_3 = {} if cache_size && cache_3.size > cache_size - count = meth.last - cache_3[signature] if count > functor_cache_base ** 2 - count += 1 - elsif meth = cache_1[signature] - cache_2 = {} if cache_size && cache_2.size > cache_size - count = meth.last - cache_2[signature] = meth if count > functor_cache_base - count += 1 + match_args = with_self ? [self] + args : args + signature = match_args.hash + if meth = c3[signature] + meth.first.bind(self).call(*args) + elsif meth = c2[signature] + c3, c2 = c2, {} if cache_size && c3.size >= cache_size + count = meth[-1] + (c3[signature] = meth && c2.delete(signature)) if count > cache_base ** 3 + meth[-1] += 1 + meth.first.bind(self).call(*args) + elsif meth = c1[signature] + c2, c1 = c1, {} if cache_size && c2.size >= cache_size + count = meth[-1] + (c2[signature] = meth && c1.delete(signature)) if count > cache_base ** 2 + meth[-1] += 1 meth.first.bind(self).call(*args) - elsif Functor.match?(args, pattern) - cache_1 = {} if cache_size && cache_1.size > cache_size - cache_1[signature] = [newest, 0] + elsif meth = c0[signature] + c1, c0 = c0, {} if cache_size && c1.size >= cache_size + count = meth[-1] + (c1[signature] = meth && c0.delete(signature)) if count > cache_base + meth[-1] += 1 + meth.first.bind(self).call(*args) + elsif Functor.match?(match_args, pattern) + c0 = {} if cache_size && c0.size >= cache_size + c0[signature] = [newest, 0] newest.bind(self).call(*args) elsif old old.bind(self).call(*args) @@ -75,34 +86,12 @@ def k.functor( name, *pattern, &action ) end end - def k.functor_with_self( name, *pattern, &action ) - name = name.to_s - cache = (functor_cache[name] ||= {}) - cache_size = functor_cache_size - old = instance_method(name) if instance_methods.include?( name ) - define_method( name, action ) - newest = instance_method(name) - define_method( name ) do | *args | - s_args = [self] + args - signature = (s_args).hash - if meth = cache[signature] - meth.bind(self).call(*args) - elsif Functor.match?(s_args, pattern) - cache = {} if cache_size && cache.size > cache_size - cache[signature] = newest - newest.bind(self).call(*args) - elsif old - old.bind(self).call(*args) - else - raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) - end - end - end - end end + # Stuff for using standalone instances of Functor + # When creating a functor instance, use given within the block to add actions def initialize( &block ) yield( self ) if block_given? end From 952f6e466f774e7216a64dfcb79a7bb8b3d954f8 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 18:58:14 -0600 Subject: [PATCH 24/42] tunable tiered cache --- lib/functor.rb | 7 +++---- metrics/one_arg.rb | 25 ++++++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index e6003bf..7cf82cf 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -13,7 +13,6 @@ def functor_cache def self.included( k ) def k.functor_cache; @functor_cache ||= [{},{},{},{}]; end - def k.clear_functor_cache; @functor_cache = [{},{},{},{}]; end def k.functor_cache_config(options={}) @functor_cache_size = options[:size] if options[:size] @@ -57,19 +56,19 @@ def k._functor( name, with_self=false, *pattern, &action) if meth = c3[signature] meth.first.bind(self).call(*args) elsif meth = c2[signature] - c3, c2 = c2, {} if cache_size && c3.size >= cache_size + c0, c1, c2, c3 = c1, c2, c3, {} if cache_size && c3.size >= cache_size count = meth[-1] (c3[signature] = meth && c2.delete(signature)) if count > cache_base ** 3 meth[-1] += 1 meth.first.bind(self).call(*args) elsif meth = c1[signature] - c2, c1 = c1, {} if cache_size && c2.size >= cache_size + c0, c1, c2 = c1, c2, {} if cache_size && c2.size >= cache_size count = meth[-1] (c2[signature] = meth && c1.delete(signature)) if count > cache_base ** 2 meth[-1] += 1 meth.first.bind(self).call(*args) elsif meth = c0[signature] - c1, c0 = c0, {} if cache_size && c1.size >= cache_size + c0, c1 = c1, {} if cache_size && c1.size >= cache_size count = meth[-1] (c1[signature] = meth && c0.delete(signature)) if count > cache_base meth[-1] += 1 diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index a923e96..df57e37 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -2,7 +2,7 @@ class A include Functor::Method - functor_cache_size 1000 + functor_cache_config :size => 700, :base => 5 functor( :foo, Integer ) { |x| :integer } functor( :foo, String ) { |x| :string } @@ -27,20 +27,25 @@ def foo(x) class OneArg < Steve before do - nums = (1..100).to_a - alphas = ("a".."cv").to_a - @args = nums + nums.map { |i| i.to_f } + alphas + alphas.map { |i| i.to_sym } + Array.new(100, "one") + nums = (1..200).to_a + alphas = ("a".."gr").to_a + @args_set = nums + nums.map { |i| i.to_f } + alphas + alphas.map { |i| i.to_sym } + Array.new(200, "one") + @args = [] + srand(46) + 9000.times { @args << @args_set[rand(@args_set.size)] } end end OneArg.new "functor method" do before_sample do - @a = A.new + @a = A.new end measure do - 10.times do - @args.each { |item| @a.foo item } - end + @args.each { |item| @a.foo item } + end + + after_sample do + puts A.functor_cache.map{ |c| c["foo"].size }.inspect end end @@ -49,9 +54,7 @@ class OneArg < Steve @n = Native.new end measure do - 10.times do - @args.each { |item| @n.foo item } - end + @args.each { |item| @n.foo item } end end From aada4808bb5e2aa9300c9a5389f4b645870a633c Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 19:16:49 -0600 Subject: [PATCH 25/42] do threshold calcs outside of The Method --- lib/functor.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 7cf82cf..1c42ede 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -47,6 +47,7 @@ def k._functor( name, with_self=false, *pattern, &action) name = name.to_s c0,c1,c2,c3 = (0..3).map { |i| functor_cache[i][name] ||= {} } cache_size, cache_base = functor_cache_size, functor_cache_base + c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) newest = instance_method(name) @@ -56,21 +57,22 @@ def k._functor( name, with_self=false, *pattern, &action) if meth = c3[signature] meth.first.bind(self).call(*args) elsif meth = c2[signature] + # when c3 fills up, shift its contents down to c2, and so forth c0, c1, c2, c3 = c1, c2, c3, {} if cache_size && c3.size >= cache_size count = meth[-1] - (c3[signature] = meth && c2.delete(signature)) if count > cache_base ** 3 + (c3[signature] = meth && c2.delete(signature)) if count > c3_thresh meth[-1] += 1 meth.first.bind(self).call(*args) elsif meth = c1[signature] c0, c1, c2 = c1, c2, {} if cache_size && c2.size >= cache_size count = meth[-1] - (c2[signature] = meth && c1.delete(signature)) if count > cache_base ** 2 + (c2[signature] = meth && c1.delete(signature)) if count > c2_thresh meth[-1] += 1 meth.first.bind(self).call(*args) elsif meth = c0[signature] c0, c1 = c1, {} if cache_size && c1.size >= cache_size count = meth[-1] - (c1[signature] = meth && c0.delete(signature)) if count > cache_base + (c1[signature] = meth && c0.delete(signature)) if count > c1_thresh meth[-1] += 1 meth.first.bind(self).call(*args) elsif Functor.match?(match_args, pattern) From ef7c2bbe1facffe92a2eeb6fabbce7f840bc1c90 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 20:48:17 -0600 Subject: [PATCH 26/42] document inlined ugliness with comments --- lib/functor.rb | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 1c42ede..4f65416 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -48,39 +48,45 @@ def k._functor( name, with_self=false, *pattern, &action) c0,c1,c2,c3 = (0..3).map { |i| functor_cache[i][name] ||= {} } cache_size, cache_base = functor_cache_size, functor_cache_base c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i + # Grab the current incarnation of The Method old = instance_method(name) if instance_methods.include?( name ) define_method( name, action ) + # Grab the newly redefined version of The Method newest = instance_method(name) + + # Recursively redefine The Method using the newest and previous incarnations define_method( name ) do | *args | match_args = with_self ? [self] + args : args signature = match_args.hash + # chech caches in order of priority. Inlined ugliness makes for speed if meth = c3[signature] - meth.first.bind(self).call(*args) + meth[0].bind(self).call(*args) elsif meth = c2[signature] # when c3 fills up, shift its contents down to c2, and so forth c0, c1, c2, c3 = c1, c2, c3, {} if cache_size && c3.size >= cache_size - count = meth[-1] - (c3[signature] = meth && c2.delete(signature)) if count > c3_thresh + # methods are cached as [ method, counter ] + c3[signature] = c2.delete(signature) if meth[-1] > c3_thresh meth[-1] += 1 - meth.first.bind(self).call(*args) + meth[0].bind(self).call(*args) elsif meth = c1[signature] c0, c1, c2 = c1, c2, {} if cache_size && c2.size >= cache_size - count = meth[-1] - (c2[signature] = meth && c1.delete(signature)) if count > c2_thresh + c2[signature] = c1.delete(signature) if meth[-1] > c2_thresh meth[-1] += 1 - meth.first.bind(self).call(*args) + meth[0].bind(self).call(*args) elsif meth = c0[signature] c0, c1 = c1, {} if cache_size && c1.size >= cache_size - count = meth[-1] - (c1[signature] = meth && c0.delete(signature)) if count > c1_thresh + c1[signature] = c0.delete(signature) if meth[-1] > c1_thresh meth[-1] += 1 - meth.first.bind(self).call(*args) + meth[0].bind(self).call(*args) + # On cache miss, call the newest incarnation if we match the topmost pattern elsif Functor.match?(match_args, pattern) c0 = {} if cache_size && c0.size >= cache_size c0[signature] = [newest, 0] newest.bind(self).call(*args) + # or call the previous incarnation of The Method elsif old old.bind(self).call(*args) + # and if there are no older incarnations, whine about it else raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) end From c345ff145c74857900ac9d82e17c4beb4523677c Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 17 Jan 2009 21:00:25 -0600 Subject: [PATCH 27/42] unified cache config as hash; it is settable at two levels --- lib/functor.rb | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 4f65416..c63df8b 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -4,35 +4,20 @@ class Functor + def self.cache_config(options={}) + (@cache_config ||= { :size => 10_000, :base => 10 }).merge!(options) + end + module Method - def functor_cache - self.class.functor_cache - end - def self.included( k ) def k.functor_cache; @functor_cache ||= [{},{},{},{}]; end def k.functor_cache_config(options={}) - @functor_cache_size = options[:size] if options[:size] - @functor_cache_base = options[:base] if options[:base] - end - - def k.functor_cache_size(val=nil) - @functor_cache_size ||= val + (@functor_cache_config ||= Functor.cache_config).merge!(options) end - - def k.functor_cache_base(val=nil) - @functor_cache_base || @functor_cache_base = ( val || 16 ) - end - - def k.functor_cache_0; functor_cache[0]; end - def k.functor_cache_1; functor_cache[1]; end - def k.functor_cache_2; functor_cache[2]; end - def k.functor_cache_3; functor_cache[3]; end - def k.functor( name, *pattern, &action ) _functor( name, false, *pattern, &action) end @@ -46,7 +31,7 @@ def k.functor_with_self( name, *pattern, &action ) def k._functor( name, with_self=false, *pattern, &action) name = name.to_s c0,c1,c2,c3 = (0..3).map { |i| functor_cache[i][name] ||= {} } - cache_size, cache_base = functor_cache_size, functor_cache_base + cache_size, cache_base = functor_cache_config[:size], functor_cache_config[:base] c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i # Grab the current incarnation of The Method old = instance_method(name) if instance_methods.include?( name ) From 5d1ea80fcba827c86f524ce6c94b0479a5408082 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 21 Jan 2009 09:40:55 -0600 Subject: [PATCH 28/42] wildcard using methods starting with _ --- lib/functor.rb | 8 ++++++++ test/wildcard.rb | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 test/wildcard.rb diff --git a/lib/functor.rb b/lib/functor.rb index c63df8b..5696bda 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -26,6 +26,14 @@ def k.functor_with_self( name, *pattern, &action ) _functor( name, true, *pattern, &action) end + def k.method_missing(name, *args) + if args.empty? && name.to_s =~ /^_/ + lambda { true } + else + super + end + end + private def k._functor( name, with_self=false, *pattern, &action) diff --git a/test/wildcard.rb b/test/wildcard.rb new file mode 100644 index 0000000..7c91093 --- /dev/null +++ b/test/wildcard.rb @@ -0,0 +1,15 @@ +require "#{File.dirname(__FILE__)}/helpers" + +class Wildcard + include Functor::Method + functor( :foo, Integer, _whatever ) { |int, whatever| "#{int}: #{whatever}" } +end + +describe "A functor" do + + it "can use a method beginning with '_' to match anything" do + c = Wildcard.new + c.foo( 7, "Smurf").should == "7: Smurf" + end + +end \ No newline at end of file From 8da4ccfd0d0a9395a3118287afc471393bc1601a Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 23 Jan 2009 10:23:33 -0600 Subject: [PATCH 29/42] rm original test file --- test.rb | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 test.rb diff --git a/test.rb b/test.rb deleted file mode 100644 index 9c2630b..0000000 --- a/test.rb +++ /dev/null @@ -1,24 +0,0 @@ -load 'lib/functor.rb' -class A - include Functor::Method - functor( :foo, Integer ) { |x| [ A, Integer ] } - functor( :foo, String ) { |s| [ A, String ] } - functor( :foo, Float ) { |h| [ A, Float ] } -end - -class B < A - functor( :foo, String ) { |s| [ B, String ] } - functor( :foo, Float ) { |f| [ B, *A.functors[:foo].apply( self, f ) ] } -end - -a = A.new ; b = B.new -puts a.foo( 7 ).inspect -puts b.foo( 'tentacles' ).inspect - -fib ||= Functor.new do - given( 0 ) { 0 } - given( 1 ) { 1 } - given( Integer ) { | n | self.call( n - 1 ) + self.call( n - 2 ) } -end - -puts fib[ 7 ] \ No newline at end of file From d3061832957459e7ac3d63c2305b51d3b28c0251 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 23 Jan 2009 10:24:01 -0600 Subject: [PATCH 30/42] Functor::NoMatch instead of ArgumentError --- lib/functor.rb | 6 +++++- test/functor.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 5696bda..45991e2 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -4,6 +4,10 @@ class Functor + class NoMatch < ArgumentError + + end + def self.cache_config(options={}) (@cache_config ||= { :size => 10_000, :base => 10 }).merge!(options) end @@ -81,7 +85,7 @@ def k._functor( name, with_self=false, *pattern, &action) old.bind(self).call(*args) # and if there are no older incarnations, whine about it else - raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) + raise NoMatch.new( "No functor matches the given arguments for method :#{name}." ) end end end diff --git a/test/functor.rb b/test/functor.rb index dcb4899..47a78e7 100644 --- a/test/functor.rb +++ b/test/functor.rb @@ -24,7 +24,7 @@ class Repeater end specify "raise an exception if there is no matching value" do - lambda { @r.repeat( 7.3 ) }.should.raise(ArgumentError) + lambda { @r.repeat( 7.3 ) }.should.raise(Functor::NoMatch) end end From 16545bee772885c5912633a618919480a9a710cb Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sat, 31 Jan 2009 16:53:16 -0600 Subject: [PATCH 31/42] Bug: closure defined c3 lvar, so threshold test fails reliably --- lib/functor.rb | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 45991e2..6fc90bc 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -18,6 +18,10 @@ def self.included( k ) def k.functor_cache; @functor_cache ||= [{},{},{},{}]; end + def k.f_cache_2 + @f_cache_2 ||= Hash.new { |hash, key| hash[key] = [ {},{},{},{} ] } + end + def k.functor_cache_config(options={}) (@functor_cache_config ||= Functor.cache_config).merge!(options) end @@ -42,7 +46,7 @@ def k.method_missing(name, *args) def k._functor( name, with_self=false, *pattern, &action) name = name.to_s - c0,c1,c2,c3 = (0..3).map { |i| functor_cache[i][name] ||= {} } + name_cache = f_cache_2[name] cache_size, cache_base = functor_cache_config[:size], functor_cache_config[:base] c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i # Grab the current incarnation of The Method @@ -55,30 +59,30 @@ def k._functor( name, with_self=false, *pattern, &action) define_method( name ) do | *args | match_args = with_self ? [self] + args : args signature = match_args.hash - # chech caches in order of priority. Inlined ugliness makes for speed - if meth = c3[signature] + # check caches in order of priority. Inlined ugliness makes for speed + if meth = name_cache[3][signature] meth[0].bind(self).call(*args) - elsif meth = c2[signature] + elsif meth = name_cache[2][signature] # when c3 fills up, shift its contents down to c2, and so forth - c0, c1, c2, c3 = c1, c2, c3, {} if cache_size && c3.size >= cache_size + (name_cache[0], name_cache[1], name_cache[2], name_cache[3] = name_cache[1], name_cache[2], name_cache[3], {}) if name_cache[3].size >= cache_size # methods are cached as [ method, counter ] - c3[signature] = c2.delete(signature) if meth[-1] > c3_thresh + name_cache[3][signature] = name_cache[2].delete(signature) if meth[-1] > c3_thresh meth[-1] += 1 meth[0].bind(self).call(*args) - elsif meth = c1[signature] - c0, c1, c2 = c1, c2, {} if cache_size && c2.size >= cache_size - c2[signature] = c1.delete(signature) if meth[-1] > c2_thresh + elsif meth = name_cache[1][signature] + name_cache[0], name_cache[1], name_cache[2] = name_cache[1], name_cache[2], {} if name_cache[2].size >= cache_size * 2 + name_cache[2][signature] = name_cache[1].delete(signature) if meth[-1] > c2_thresh meth[-1] += 1 meth[0].bind(self).call(*args) - elsif meth = c0[signature] - c0, c1 = c1, {} if cache_size && c1.size >= cache_size - c1[signature] = c0.delete(signature) if meth[-1] > c1_thresh + elsif meth = name_cache[0][signature] + name_cache[0], name_cache[1] = name_cache[1], {} if name_cache[1].size >= cache_size * 4 + name_cache[1][signature] = name_cache[0].delete(signature) if meth[-1] > c1_thresh meth[-1] += 1 meth[0].bind(self).call(*args) # On cache miss, call the newest incarnation if we match the topmost pattern elsif Functor.match?(match_args, pattern) - c0 = {} if cache_size && c0.size >= cache_size - c0[signature] = [newest, 0] + name_cache[0] = {} if name_cache[0].size >= cache_size * 8 + name_cache[0][signature] = [newest, 0] newest.bind(self).call(*args) # or call the previous incarnation of The Method elsif old From aa3255a6862f0d7c479fc003fdc27d4a029dfe1b Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 09:57:34 -0600 Subject: [PATCH 32/42] benchmark debugging --- metrics/one_arg.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index df57e37..82e1235 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -45,7 +45,7 @@ class OneArg < Steve end after_sample do - puts A.functor_cache.map{ |c| c["foo"].size }.inspect + puts A.f_cache_2["foo"].map{ |c| c.size }.inspect end end @@ -58,4 +58,4 @@ class OneArg < Steve end end -OneArg.compare_instances( 4, 8) \ No newline at end of file +OneArg.compare_instances( 4, 128) \ No newline at end of file From f7942ea7a46a1e22b43d8f82af53813029d6d4f8 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 11:01:21 -0600 Subject: [PATCH 33/42] rewrote caching to insert sig/meth into top tier --- lib/functor.rb | 77 +++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 6fc90bc..ba3f210 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -15,15 +15,13 @@ def self.cache_config(options={}) module Method def self.included( k ) - - def k.functor_cache; @functor_cache ||= [{},{},{},{}]; end - - def k.f_cache_2 - @f_cache_2 ||= Hash.new { |hash, key| hash[key] = [ {},{},{},{} ] } + + def k.functor_cache + @functor_cache ||= Hash.new { |hash, key| hash[key] = [ {},{},{},{} ] } end def k.functor_cache_config(options={}) - (@functor_cache_config ||= Functor.cache_config).merge!(options) + @functor_cache_config = ( @functor_cache_config || Functor.cache_config ).merge(options) end def k.functor( name, *pattern, &action ) @@ -35,60 +33,49 @@ def k.functor_with_self( name, *pattern, &action ) end def k.method_missing(name, *args) - if args.empty? && name.to_s =~ /^_/ - lambda { true } - else - super - end + args.empty? && name.to_s =~ /^_/ ? lambda { true} : super end private def k._functor( name, with_self=false, *pattern, &action) name = name.to_s - name_cache = f_cache_2[name] + mc = functor_cache[name] # grab the cache tiers for The Method cache_size, cache_base = functor_cache_config[:size], functor_cache_config[:base] + c0_size, c1_size, c2_size, c3_size = cache_size * 4, cache_size * 3, cache_size * 2, cache_size c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i - # Grab the current incarnation of The Method - old = instance_method(name) if instance_methods.include?( name ) - define_method( name, action ) - # Grab the newly redefined version of The Method - newest = instance_method(name) + old_method = instance_method(name) if instance_methods.include?( name ) # grab The Method's current incarnation + define_method( name, action ) # redefine The Method + newest = instance_method(name) # grab newly redefined The Method # Recursively redefine The Method using the newest and previous incarnations define_method( name ) do | *args | match_args = with_self ? [self] + args : args - signature = match_args.hash - # check caches in order of priority. Inlined ugliness makes for speed - if meth = name_cache[3][signature] + sig = match_args.hash + if meth = mc[3][sig] # check caches from top down meth[0].bind(self).call(*args) - elsif meth = name_cache[2][signature] - # when c3 fills up, shift its contents down to c2, and so forth - (name_cache[0], name_cache[1], name_cache[2], name_cache[3] = name_cache[1], name_cache[2], name_cache[3], {}) if name_cache[3].size >= cache_size - # methods are cached as [ method, counter ] - name_cache[3][signature] = name_cache[2].delete(signature) if meth[-1] > c3_thresh - meth[-1] += 1 + elsif meth = mc[2][sig] + meth[1] += 1 # increment hit count + mc[3][sig] = mc[2].delete(sig) if meth[1] > c3_thresh # promote sig if it has enough hits + (mc[0], mc[1], mc[2], mc[3] = mc[1], mc[2], mc[3], {}) if mc[3].size >= c3_size # cascade if c3 is full meth[0].bind(self).call(*args) - elsif meth = name_cache[1][signature] - name_cache[0], name_cache[1], name_cache[2] = name_cache[1], name_cache[2], {} if name_cache[2].size >= cache_size * 2 - name_cache[2][signature] = name_cache[1].delete(signature) if meth[-1] > c2_thresh - meth[-1] += 1 + elsif meth = mc[1][sig] + meth[1] += 1 + mc[2][sig] = mc[1].delete(sig) if meth[1] > c2_thresh + mc[0], mc[1], mc[2] = mc[1], mc[2], {} if mc[2].size >= c2_size meth[0].bind(self).call(*args) - elsif meth = name_cache[0][signature] - name_cache[0], name_cache[1] = name_cache[1], {} if name_cache[1].size >= cache_size * 4 - name_cache[1][signature] = name_cache[0].delete(signature) if meth[-1] > c1_thresh - meth[-1] += 1 + elsif meth = mc[0][sig] + meth[1] += 1 + mc[1][sig] = mc[0].delete(sig) if meth[1] > c1_thresh + mc[0], mc[1] = mc[1], {} if mc[1].size >= c1_size meth[0].bind(self).call(*args) - # On cache miss, call the newest incarnation if we match the topmost pattern - elsif Functor.match?(match_args, pattern) - name_cache[0] = {} if name_cache[0].size >= cache_size * 8 - name_cache[0][signature] = [newest, 0] + elsif Functor.match?(match_args, pattern) # not cached? Try newest meth/pat. + (mc[0], mc[1], mc[2], mc[3] = mc[1], mc[2], mc[3], {}) if mc[3].size >= c3_size + mc[3][sig] = [newest, 0] # methods are cached as [ method, counter ] newest.bind(self).call(*args) - # or call the previous incarnation of The Method - elsif old - old.bind(self).call(*args) - # and if there are no older incarnations, whine about it - else + elsif old_method # or call the previous incarnation of The Method + old_method.bind(self).call(*args) + else # and if there are no older incarnations, whine about it raise NoMatch.new( "No functor matches the given arguments for method :#{name}." ) end end @@ -133,8 +120,8 @@ def []( *args, &block ) def to_proc ; lambda { |*args| self.call( *args ) } ; end def self.match?( args, pattern ) - args.all? do |a| - p = pattern[args.index(a)]; p === a || ( p.respond_to?(:call) && p.call(a)) + args.all? do |arg| + pat = pattern[args.index(arg)]; pat === arg || ( pat.respond_to?(:call) && pat.call(arg)) end if args.length == pattern.length end From a10b2d0fe99bd7f93101d9193e91d95577f546bb Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 11:05:14 -0600 Subject: [PATCH 34/42] standalone Functor now uses _functor --- lib/functor.rb | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index ba3f210..5d487d8 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -88,36 +88,19 @@ def k._functor( name, with_self=false, *pattern, &action) # When creating a functor instance, use given within the block to add actions def initialize( &block ) + class << self; include Functor::Method; end yield( self ) if block_given? end def given( *pattern, &action ) - name = "call" - old = method(name) if methods.include?( name ) - class << self; self; end.instance_eval do - define_method( name, action ) - end - newest = method(name) - class << self; self; end.instance_eval do - - define_method( name ) do | *args | - if Functor.match?(args, pattern) - newest.call(*args) - elsif old - old.call(*args) - else - raise ArgumentError.new( "No functor matches the given arguments for method :#{name}." ) - end - end - - end + class << self; self; end._functor( "call", false, *pattern, &action) end def []( *args, &block ) call( *args, &block ) end - def to_proc ; lambda { |*args| self.call( *args ) } ; end + def to_proc ; lambda { |*args| call( *args ) } ; end def self.match?( args, pattern ) args.all? do |arg| From e5a118e71046644a7ce623189ef905d167dec77b Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 11:09:11 -0600 Subject: [PATCH 35/42] clarify, clean up --- lib/functor.rb | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/functor.rb b/lib/functor.rb index 5d487d8..88a69e3 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -1,12 +1,9 @@ require "#{File.dirname(__FILE__)}/object" require 'rubygems' -require 'metaid' class Functor - class NoMatch < ArgumentError - - end + class NoMatch < ArgumentError; end def self.cache_config(options={}) (@cache_config ||= { :size => 10_000, :base => 10 }).merge!(options) @@ -32,6 +29,7 @@ def k.functor_with_self( name, *pattern, &action ) _functor( name, true, *pattern, &action) end + # undefined methods beginning with '_' can be used as wildcards in Functor patterns def k.method_missing(name, *args) args.empty? && name.to_s =~ /^_/ ? lambda { true} : super end @@ -84,27 +82,23 @@ def k._functor( name, with_self=false, *pattern, &action) end end - # Stuff for using standalone instances of Functor - - # When creating a functor instance, use given within the block to add actions def initialize( &block ) class << self; include Functor::Method; end yield( self ) if block_given? end def given( *pattern, &action ) - class << self; self; end._functor( "call", false, *pattern, &action) + (class << self; self; end)._functor( "call", false, *pattern, &action) end - def []( *args, &block ) - call( *args, &block ) - end + def []( *args, &block ); call( *args, &block ); end def to_proc ; lambda { |*args| call( *args ) } ; end def self.match?( args, pattern ) args.all? do |arg| - pat = pattern[args.index(arg)]; pat === arg || ( pat.respond_to?(:call) && pat.call(arg)) + pat = pattern[args.index(arg)] + pat === arg || ( pat.respond_to?(:call) && pat.call(arg)) end if args.length == pattern.length end From dac4bb7ba2422cec326e083b13550f49c29abd0c Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 11:11:05 -0600 Subject: [PATCH 36/42] metrics tweaks --- metrics/many_args.rb | 2 +- metrics/one_arg.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/metrics/many_args.rb b/metrics/many_args.rb index ff5fb8c..32a70e3 100644 --- a/metrics/many_args.rb +++ b/metrics/many_args.rb @@ -68,4 +68,4 @@ class ManyArgs < Steve end end -ManyArgs.compare_instances( 4, 16) \ No newline at end of file +ManyArgs.compare_instances( 4, 64) \ No newline at end of file diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index 82e1235..ca15b89 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -2,7 +2,7 @@ class A include Functor::Method - functor_cache_config :size => 700, :base => 5 + functor_cache_config :size => 700, :base => 6 functor( :foo, Integer ) { |x| :integer } functor( :foo, String ) { |x| :string } @@ -58,4 +58,4 @@ class OneArg < Steve end end -OneArg.compare_instances( 4, 128) \ No newline at end of file +OneArg.compare_instances( 4, 64) \ No newline at end of file From 612cc5a6718e61b9e2e0899b9c52e7db99560a52 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 11:12:13 -0600 Subject: [PATCH 37/42] I like powers of 2 --- lib/functor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/functor.rb b/lib/functor.rb index 88a69e3..a5ddf8b 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -6,7 +6,7 @@ class Functor class NoMatch < ArgumentError; end def self.cache_config(options={}) - (@cache_config ||= { :size => 10_000, :base => 10 }).merge!(options) + (@cache_config ||= { :size => 4_096, :base => 8 }).merge!(options) end module Method From 4cbbac5bfbf4bcff6ddddbd4893c3e994f36e152 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 12:22:06 -0600 Subject: [PATCH 38/42] updated README and added section on Caching --- doc/README | 56 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/doc/README b/doc/README index 76793c0..5afbf92 100644 --- a/doc/README +++ b/doc/README @@ -8,59 +8,73 @@ To use it in a class: attr_accessor :times include Functor::Method functor( :repeat, Integer ) { |x| x * @times } - functor( :repeat, String ) { |s| [].fill( s, 0..@times ).join(' ') } + functor( :repeat, String ) { |s| [].fill( s, 0, @times ).join(' ') } end r = Repeater.new r.times = 5 r.repeat( 5 ) # => 25 r.repeat( "-" ) # => "- - - - -" - r.repeat( 7.3 ) # => ArgumentError! + r.repeat( 7.3 ) # => Functor::NoMatch! = Stand-Alone Functors You can also define Functor objects directly: - fib = Functor.new do - given( 0 ) { 0 } - given( 1 ) { 1 } - given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) } + 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 You can use functors directly with functions taking a block like this: [ *0..10 ].map( &fib ) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] -You can call a functor as a method using #apply: +You can call a functor as a method using #call: - fun.apply( obj, 7 ) + fun.call( obj, 7 ) -which is actually how the method functors are implemented. - = Pattern Matching -Arguments are matched first using === and then ==, so anything that supports these methods can be matched against. In addition, you may pass "guards," any object that responds to #call and which take and object (the argument) and return true or false. This allows you to do things like this: +Arguments are matched first using #===, so anything that supports these methods can be matched against. In addition, you may pass as a "guard" any object that responds to #call and which takes an object (the argument) and return true or false. This allows you to do things like this: stripe ||= Functor.new do given( lambda { |x| x % 2 == 0 } ) { 'white' } given( lambda { |x| x % 2 == 1 } ) { 'silver' } end - -which will return "white" and "silver" alternately for a sequence of numbers. + + stripe.call( 3 ) # => 'silver' + stripe.call( 4 ) # => 'white' = Precedence -Precedence is defined in order of declaration: first-come, first-serve, aka FIFO. Thus, you need to be careful in how you define your functor. The Fibonacci example above would not work properly if the Integer pattern was given first. That said, it is possible to redefine earlier cases, which, in effect, "demotes" it, as if it had not been declared before. So the following will work properly: +Precedence works similarly to Ruby method definition, i.e. Last In, First Out. Thus, you need to be careful in how you define your functor. The Fibonacci example above would not work properly if the Integer pattern was given last. + += Caching Options + +Methods defined with functors are substantially slower than methods defined natively with "def". To (partially) alleviate the performance hit, Functor keeps track of which functor block matches each particular *args set, allowing it to short circuit the matching process when an *args set is seen again. If the *args cache were unlimited, this would represent a serious memory leak. Naturally, we have not implemented an unlimited cache. + +Rather, the *args cache is subjected to flexible and scalable limits that also have the effect of promoting frequently encountered *args sets and allowing those less frequently encountered to languish, yea even unto death. The caching system uses two configuration parameters, which may be set for Functor or for any class which has included Functor::Method. When a class does not set these parameters, it will inherit the options set at the Functor level. Functor comes with (what we think are) sensible defaults. + +The :size parameter controls, albeit indirectly, the number of items the caching system may store. If your :size is greater than the number of *args sets the functor will encounter, you do not need to worry (or even know) about the other parameter. You can set the :size param thusly: - fib = Functor.new do - given( Integer ) { |n| raise "this would start an infinite loop ..." } - given( 0 ) { 0 } - given( 1 ) { 1 } - # but this will "demote" the Integer pattern and now it will work ... - given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) } + Functor.cache_config :size => 10_000 + + class A + include Functor::Method + functor_cache_config :size => 700 end -This isn't perfect, but it is very easy to predict, simple to implement, and reasonably fast, which other approaches (such as implementing a precedence scheme) are not. +The other parameter, :base, determines the thresholds for promotion, i.e. how many times an *args set must be seen before it "becomes more important". While the use of the :size parameter is somewhat straightforward (albeit indirect), you need some idea of how the caching system works to understand the :base param. Functor classes maintain cached *args sets in tiers. When attempting to short-circuit the matching process, Functor checks each of these caches, starting at the top and working downward. Each tier has a promotion threshold, which represents the number of hits an item must receive before it can jump to the next tier. The promotion thresholds are determined using exponents of the :base parameter. The threshold for exiting the lowest tier is (base ** 1); for the next tier, it is (base ** 2); for the next, (base ** 3). + +Assume the following configuration: + + Functor.cache_config :base => 10 + +Functor currently uses 4 tiers. Let us call them c0, c1, c2, and c3. To pass from c0 to c1, an item must receive 10 (i.e. 10**1) hits. To pass from c1 to c2, it must receive 100 (10**2) hits. For promotion to c3, it must receive 1,000 hits. A lower :base setting results in lower thresholds across all tiers. The thresholds for a base of 8 would be 8, 64, and 512. Again, if your cache :size is large enough, the promotion thresholds are irrelevant. If, however, the number of distinct *args sets you expect to encounter is larger than your desired cache size, the :base parameter allows you to tune the promotion thresholds for a better fit with the frequency distribution of the *args sets. A high :base setting means that only very high-frequency items will make it into the top cache tier. + +Tier size limits are enforced in a useful way. Each tier has a size limit. When that size is reached, all of its items are dropped into the next lower tier, which drops all of its items into the next lower tier, etc. Tier size limits increase from top to bottom, so that when c3 dumps its items into c2, ample space remains in c2. After a tier cascade, all of the items in a particular tier have a hit count higher than the promotion threshold, guaranteeing that the next hit will promote the item. The effect of this is to allow "languishing" items at a certain level to eventually drop out the bottom, while preserving the valuable, active, high-frequency items. = Credits And Support From 216ea7cdb93c3644e65dd1c05e5160283f08ed3e Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 13:27:08 -0600 Subject: [PATCH 39/42] move README to top level to appease GitHub --- doc/README => README | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/README => README (100%) diff --git a/doc/README b/README similarity index 100% rename from doc/README rename to README From 613d63f676ada46d1d3f8b3a2db0bc64c9220d24 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Tue, 3 Feb 2009 13:29:48 -0600 Subject: [PATCH 40/42] tweak debug puts --- metrics/one_arg.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metrics/one_arg.rb b/metrics/one_arg.rb index ca15b89..9275406 100644 --- a/metrics/one_arg.rb +++ b/metrics/one_arg.rb @@ -45,7 +45,7 @@ class OneArg < Steve end after_sample do - puts A.f_cache_2["foo"].map{ |c| c.size }.inspect + puts A.functor_cache["foo"].map{ |c| c.size }.inspect end end @@ -58,4 +58,4 @@ class OneArg < Steve end end -OneArg.compare_instances( 4, 64) \ No newline at end of file +OneArg.compare_instances( 4, 96) \ No newline at end of file From adeef7b14b542beeeef81b197edcd619e22d41c0 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 6 Feb 2009 16:53:19 -0600 Subject: [PATCH 41/42] ruby 1.9 compatibility fixes --- README | 8 ++++---- lib/functor.rb | 2 +- test/fib.rb | 4 ++-- test/guards.rb | 4 ++-- test/inheritance.rb | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README b/README index 5afbf92..08c27a4 100644 --- a/README +++ b/README @@ -23,8 +23,8 @@ You can also define Functor objects directly: 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 } + f.given( 0 ) { |x| 0 } + f.given( 1 ) { |x| 1 } end You can use functors directly with functions taking a block like this: @@ -40,8 +40,8 @@ You can call a functor as a method using #call: Arguments are matched first using #===, so anything that supports these methods can be matched against. In addition, you may pass as a "guard" any object that responds to #call and which takes an object (the argument) and return true or false. This allows you to do things like this: stripe ||= Functor.new do - given( lambda { |x| x % 2 == 0 } ) { 'white' } - given( lambda { |x| x % 2 == 1 } ) { 'silver' } + given( lambda { |x| x % 2 == 0 } ) { |x| 'white' } + given( lambda { |x| x % 2 == 1 } ) { |x| 'silver' } end stripe.call( 3 ) # => 'silver' diff --git a/lib/functor.rb b/lib/functor.rb index a5ddf8b..6530cc2 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -31,7 +31,7 @@ def k.functor_with_self( name, *pattern, &action ) # undefined methods beginning with '_' can be used as wildcards in Functor patterns def k.method_missing(name, *args) - args.empty? && name.to_s =~ /^_/ ? lambda { true} : super + args.empty? && name.to_s =~ /^_/ ? lambda { |args| true } : super end private diff --git a/test/fib.rb b/test/fib.rb index 696c662..f3d7824 100644 --- a/test/fib.rb +++ b/test/fib.rb @@ -2,8 +2,8 @@ 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 } + f.given( 0 ) { |x| 0 } + f.given( 1 ) { |x| 1 } end describe "Dispatch on a functor object should" do diff --git a/test/guards.rb b/test/guards.rb index 89ec9c6..d8e398f 100644 --- a/test/guards.rb +++ b/test/guards.rb @@ -4,8 +4,8 @@ before do @stripe = Functor.new do |f| - f.given( lambda { |x| x % 2 == 1 } ) { 'silver' } - f.given( lambda { |x| x % 2 == 0 } ) { 'white' } + f.given( lambda { |x| x % 2 == 1 } ) { |x| 'silver' } + f.given( lambda { |x| x % 2 == 0 } ) { |x| 'white' } end @safe_divide = Functor.new do |f| diff --git a/test/inheritance.rb b/test/inheritance.rb index 45a5e28..ad1d814 100644 --- a/test/inheritance.rb +++ b/test/inheritance.rb @@ -9,7 +9,7 @@ class Parent class Child < Parent functor( :foo, String ) { |s| [ Child, String ] } - functor( :foo, Float ) { |x| super.reverse } + functor( :foo, Float ) { |x| super(x).reverse } end describe "Functor methods should support inheritance" do From 05ed16d94d0f01d75c9c2b10914737054eff7f94 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Fri, 6 Feb 2009 16:53:51 -0600 Subject: [PATCH 42/42] smarter check for existing method --- lib/functor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/functor.rb b/lib/functor.rb index 6530cc2..f770c4b 100644 --- a/lib/functor.rb +++ b/lib/functor.rb @@ -42,7 +42,7 @@ def k._functor( name, with_self=false, *pattern, &action) cache_size, cache_base = functor_cache_config[:size], functor_cache_config[:base] c0_size, c1_size, c2_size, c3_size = cache_size * 4, cache_size * 3, cache_size * 2, cache_size c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i - old_method = instance_method(name) if instance_methods.include?( name ) # grab The Method's current incarnation + old_method = instance_method(name) if method_defined?( name ) # grab The Method's current incarnation define_method( name, action ) # redefine The Method newest = instance_method(name) # grab newly redefined The Method