functor 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/functor.rb +81 -51
- data/metrics/benchmark.rb +39 -0
- data/metrics/helpers.rb +6 -0
- data/metrics/many_args.rb +71 -0
- data/metrics/one_arg.rb +61 -0
- data/test/fib.rb +2 -2
- data/test/functor.rb +6 -4
- data/test/guards.rb +2 -2
- data/test/inheritance.rb +13 -8
- data/test/matchers.rb +4 -4
- data/test/reopening.rb +3 -3
- data/test/supplement.rb +20 -0
- data/test/wildcard.rb +15 -0
- data/test/with_self.rb +8 -3
- metadata +12 -35
- data/doc/README +0 -71
- data/doc/rdoc/classes/Functor.html +0 -207
- data/doc/rdoc/classes/Functor.src/M000001.html +0 -18
- data/doc/rdoc/classes/Functor.src/M000002.html +0 -19
- data/doc/rdoc/classes/Functor.src/M000003.html +0 -18
- data/doc/rdoc/classes/Functor.src/M000004.html +0 -19
- data/doc/rdoc/classes/Functor.src/M000005.html +0 -16
- data/doc/rdoc/classes/Functor/Method.html +0 -131
- data/doc/rdoc/classes/Functor/Method.src/M000006.html +0 -25
- data/doc/rdoc/classes/Object.html +0 -147
- data/doc/rdoc/classes/Object.src/M000007.html +0 -25
- data/doc/rdoc/created.rid +0 -1
- data/doc/rdoc/files/doc/HISTORY.html +0 -111
- data/doc/rdoc/files/doc/README.html +0 -210
- data/doc/rdoc/files/lib/functor_rb.html +0 -108
- data/doc/rdoc/files/lib/object_rb.html +0 -101
- data/doc/rdoc/fr_class_index.html +0 -29
- data/doc/rdoc/fr_file_index.html +0 -30
- data/doc/rdoc/fr_method_index.html +0 -33
- data/doc/rdoc/index.html +0 -24
- data/doc/rdoc/rdoc-style.css +0 -208
- data/lib/object.rb +0 -17
data/lib/functor.rb
CHANGED
@@ -1,72 +1,102 @@
|
|
1
|
-
require "#{File.dirname(__FILE__)}/object"
|
2
|
-
|
3
1
|
class Functor
|
4
2
|
|
3
|
+
class NoMatch < ArgumentError; end
|
4
|
+
|
5
|
+
def self.cache_config(options={})
|
6
|
+
(@cache_config ||= { :size => 4_096, :base => 8 }).merge!(options)
|
7
|
+
end
|
8
|
+
|
5
9
|
module Method
|
6
|
-
|
7
|
-
r = {} ; functors.each do | name, functor |
|
8
|
-
r[ name ] = functor.clone
|
9
|
-
end
|
10
|
-
return r
|
11
|
-
end
|
10
|
+
|
12
11
|
def self.included( k )
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
|
13
|
+
def k.functor_cache
|
14
|
+
@functor_cache ||= Hash.new { |hash, key| hash[key] = [ {},{},{},{} ] }
|
15
|
+
end
|
16
|
+
|
17
|
+
def k.functor_cache_config(options={})
|
18
|
+
@functor_cache_config = ( @functor_cache_config || Functor.cache_config ).merge(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def k.functor( name, *pattern, &action )
|
22
|
+
_functor( name, false, *pattern, &action)
|
16
23
|
end
|
17
|
-
|
18
|
-
|
19
|
-
(
|
20
|
-
( functors[ name ] = Functor.new ) ) ).given( *args, &block )
|
21
|
-
define_method( name ) { | *args | instance_exec( *args, &f.match( *args ) ) }
|
24
|
+
|
25
|
+
def k.functor_with_self( name, *pattern, &action )
|
26
|
+
_functor( name, true, *pattern, &action)
|
22
27
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
define_method( name ) { | *args | instance_exec( *args, &f.match( self, *args ) ) }
|
28
|
+
|
29
|
+
# undefined methods beginning with '_' can be used as wildcards in Functor patterns
|
30
|
+
def k.method_missing(name, *args)
|
31
|
+
args.empty? && name.to_s =~ /^_/ ? lambda { |args| true } : super
|
28
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def k._functor( name, with_self=false, *pattern, &action)
|
37
|
+
name = name.to_s
|
38
|
+
mc = functor_cache[name] # grab the cache tiers for The Method
|
39
|
+
cache_size, cache_base = functor_cache_config[:size], functor_cache_config[:base]
|
40
|
+
c0_size, c1_size, c2_size, c3_size = cache_size * 4, cache_size * 3, cache_size * 2, cache_size
|
41
|
+
c1_thresh,c2_thresh,c3_thresh = cache_base.to_i, (cache_base ** 2).to_i, (cache_base ** 3).to_i
|
42
|
+
old_method = instance_method(name) if method_defined?( name ) # grab The Method's current incarnation
|
43
|
+
define_method( name, action ) # redefine The Method
|
44
|
+
newest = instance_method(name) # grab newly redefined The Method
|
45
|
+
|
46
|
+
# Recursively redefine The Method using the newest and previous incarnations
|
47
|
+
define_method( name ) do | *args |
|
48
|
+
match_args = with_self ? [self] + args : args
|
49
|
+
sig = match_args.hash
|
50
|
+
if meth = mc[3][sig] # check caches from top down
|
51
|
+
meth[0].bind(self).call(*args)
|
52
|
+
elsif meth = mc[2][sig]
|
53
|
+
meth[1] += 1 # increment hit count
|
54
|
+
mc[3][sig] = mc[2].delete(sig) if meth[1] > c3_thresh # promote sig if it has enough hits
|
55
|
+
(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
|
56
|
+
meth[0].bind(self).call(*args)
|
57
|
+
elsif meth = mc[1][sig]
|
58
|
+
meth[1] += 1
|
59
|
+
mc[2][sig] = mc[1].delete(sig) if meth[1] > c2_thresh
|
60
|
+
mc[0], mc[1], mc[2] = mc[1], mc[2], {} if mc[2].size >= c2_size
|
61
|
+
meth[0].bind(self).call(*args)
|
62
|
+
elsif meth = mc[0][sig]
|
63
|
+
meth[1] += 1
|
64
|
+
mc[1][sig] = mc[0].delete(sig) if meth[1] > c1_thresh
|
65
|
+
mc[0], mc[1] = mc[1], {} if mc[1].size >= c1_size
|
66
|
+
meth[0].bind(self).call(*args)
|
67
|
+
elsif Functor.match?(match_args, pattern) # not cached? Try newest meth/pat.
|
68
|
+
(mc[0], mc[1], mc[2], mc[3] = mc[1], mc[2], mc[3], {}) if mc[3].size >= c3_size
|
69
|
+
mc[3][sig] = [newest, 0] # methods are cached as [ method, counter ]
|
70
|
+
newest.bind(self).call(*args)
|
71
|
+
elsif old_method # or call the previous incarnation of The Method
|
72
|
+
old_method.bind(self).call(*args)
|
73
|
+
else # and if there are no older incarnations, whine about it
|
74
|
+
raise NoMatch.new( "No functor matches the given arguments for method :#{name}." )
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
29
79
|
end
|
30
80
|
end
|
31
81
|
|
32
|
-
|
33
82
|
def initialize( &block )
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def initialize_copy( from )
|
38
|
-
@rules = from.instance_eval { @rules.clone }
|
83
|
+
class << self; include Functor::Method; end
|
84
|
+
yield( self ) if block_given?
|
39
85
|
end
|
40
86
|
|
41
87
|
def given( *pattern, &action )
|
42
|
-
|
88
|
+
(class << self; self; end)._functor( "call", false, *pattern, &action)
|
43
89
|
end
|
44
90
|
|
45
|
-
def call( *args, &block )
|
46
|
-
match( *args, &block ).call( *args )
|
47
|
-
end
|
48
|
-
|
49
|
-
def []( *args, &block )
|
50
|
-
call( *args, &block )
|
51
|
-
end
|
91
|
+
def []( *args, &block ); call( *args, &block ); end
|
52
92
|
|
53
|
-
def to_proc ; lambda { |*args|
|
93
|
+
def to_proc ; lambda { |*args| call( *args ) } ; end
|
54
94
|
|
55
|
-
def match(
|
56
|
-
args
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def match?( args, pattern )
|
65
|
-
args.zip( pattern ).all? { | arg, rule | pair?( arg, rule ) } if args.length == pattern.length
|
66
|
-
end
|
67
|
-
|
68
|
-
def pair?( arg, rule )
|
69
|
-
( rule.respond_to? :call and rule.call( arg ) ) or rule === arg
|
95
|
+
def self.match?( args, pattern )
|
96
|
+
args.all? do |arg|
|
97
|
+
pat = pattern[args.index(arg)]
|
98
|
+
pat === arg || ( pat.respond_to?(:call) && pat.call(arg))
|
99
|
+
end if args.length == pattern.length
|
70
100
|
end
|
71
101
|
|
72
102
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
$:.unshift "#{here = File.dirname(__FILE__)}/stevedore/lib"
|
4
|
+
$:.unshift "#{here}/../lib"
|
5
|
+
require 'stevedore'
|
6
|
+
require 'functor'
|
7
|
+
|
8
|
+
|
9
|
+
class FuncFib < Steve
|
10
|
+
|
11
|
+
subject "Fibonacci using Functor"
|
12
|
+
|
13
|
+
# power 0.8
|
14
|
+
# sig_level 0.05
|
15
|
+
delta 0.01
|
16
|
+
|
17
|
+
before do
|
18
|
+
@fib ||= Functor.new do |f|
|
19
|
+
f.given( Integer ) { | n | f.call( n - 1 ) + f.call( n - 2 ) }
|
20
|
+
f.given( 0 ) { 0 }
|
21
|
+
f.given( 1 ) { 1 }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
fib_8 = FuncFib.new "f(8)" do
|
27
|
+
measure do
|
28
|
+
64.times { @fib.call(8) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
fib_16 = FuncFib.new "f(16)" do
|
33
|
+
measure do
|
34
|
+
2.times { @fib.call(16) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# FuncFib.recommend_test_size( 8, 16)
|
39
|
+
FuncFib.compare_instances( 8, 128)
|
data/metrics/helpers.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "#{here = File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
class A
|
4
|
+
include Functor::Method
|
5
|
+
functor( :foo, 1, 2, 3, 4, 5, 6, 7 ) { |*x| "ints" }
|
6
|
+
functor( :foo, :a, :b, :c, :d, :e, :f, :g ) { |*x| "symbols" }
|
7
|
+
functor( :foo, *%w{ a b c d e f g } ) { |*x| "strings" }
|
8
|
+
functor( :foo, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 ) { |*x| "floats" }
|
9
|
+
functor( :foo, *Array.new(7, "one") ) { |*x| "ones" }
|
10
|
+
end
|
11
|
+
|
12
|
+
class Native
|
13
|
+
def foo(*args)
|
14
|
+
case args
|
15
|
+
when [1, 2, 3, 4, 5, 6, 7]
|
16
|
+
"ints"
|
17
|
+
when [:a, :b, :c, :d, :e, :f, :g]
|
18
|
+
"symbols"
|
19
|
+
when %w{ a b c d e f g }
|
20
|
+
"strings"
|
21
|
+
when [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
|
22
|
+
"floats"
|
23
|
+
when Array.new(7, "one")
|
24
|
+
"ones"
|
25
|
+
else
|
26
|
+
raise ArgumentError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ManyArgs < Steve
|
32
|
+
end
|
33
|
+
|
34
|
+
ManyArgs.new "native method" do
|
35
|
+
before do
|
36
|
+
@args = [
|
37
|
+
[1, 2, 3, 4, 5, 6, 7],
|
38
|
+
[:a, :b, :c, :d, :e, :f, :g],
|
39
|
+
%w{ a b c d e f g },
|
40
|
+
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],
|
41
|
+
Array.new(7, "one")
|
42
|
+
]
|
43
|
+
@n = Native.new
|
44
|
+
end
|
45
|
+
measure do
|
46
|
+
400.times do
|
47
|
+
@args.each { |args| @n.foo *args }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
ManyArgs.new "functor method" do
|
54
|
+
before do
|
55
|
+
@args = [
|
56
|
+
[1, 2, 3, 4, 5, 6, 7],
|
57
|
+
[:a, :b, :c, :d, :e, :f, :g],
|
58
|
+
%w{ a b c d e f g },
|
59
|
+
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],
|
60
|
+
Array.new(7, "one")
|
61
|
+
]
|
62
|
+
@a = A.new
|
63
|
+
end
|
64
|
+
measure do
|
65
|
+
400.times do
|
66
|
+
@args.each { |args| @a.foo *args }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
ManyArgs.compare_instances( 4, 64)
|
data/metrics/one_arg.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require "#{here = File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
class A
|
4
|
+
include Functor::Method
|
5
|
+
functor_cache_config :size => 700, :base => 6
|
6
|
+
|
7
|
+
functor( :foo, Integer ) { |x| :integer }
|
8
|
+
functor( :foo, String ) { |x| :string }
|
9
|
+
functor( :foo, Float ) { |x| :float }
|
10
|
+
functor( :foo, Symbol ) { |x| :symbol }
|
11
|
+
functor( :foo, "one" ) { |x| :one }
|
12
|
+
end
|
13
|
+
|
14
|
+
class Native
|
15
|
+
def foo(x)
|
16
|
+
case x
|
17
|
+
when Integer then :integer
|
18
|
+
when String then :string
|
19
|
+
when Float then :float
|
20
|
+
when Symbol then :symbol
|
21
|
+
when "one" then :one
|
22
|
+
else
|
23
|
+
raise ArgumentError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OneArg < Steve
|
29
|
+
before do
|
30
|
+
nums = (1..200).to_a
|
31
|
+
alphas = ("a".."gr").to_a
|
32
|
+
@args_set = nums + nums.map { |i| i.to_f } + alphas + alphas.map { |i| i.to_sym } + Array.new(200, "one")
|
33
|
+
@args = []
|
34
|
+
srand(46)
|
35
|
+
9000.times { @args << @args_set[rand(@args_set.size)] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
OneArg.new "functor method" do
|
40
|
+
before_sample do
|
41
|
+
@a = A.new
|
42
|
+
end
|
43
|
+
measure do
|
44
|
+
@args.each { |item| @a.foo item }
|
45
|
+
end
|
46
|
+
|
47
|
+
after_sample do
|
48
|
+
puts A.functor_cache["foo"].map{ |c| c.size }.inspect
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
OneArg.new "native method" do
|
53
|
+
before_sample do
|
54
|
+
@n = Native.new
|
55
|
+
end
|
56
|
+
measure do
|
57
|
+
@args.each { |item| @n.foo item }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
OneArg.compare_instances( 4, 96)
|
data/test/fib.rb
CHANGED
@@ -2,8 +2,8 @@ require "#{File.dirname(__FILE__)}/helpers"
|
|
2
2
|
|
3
3
|
fib ||= Functor.new do |f|
|
4
4
|
f.given( Integer ) { | n | f.call( n - 1 ) + f.call( n - 2 ) }
|
5
|
-
f.given( 0 ) { 0 }
|
6
|
-
f.given( 1 ) { 1 }
|
5
|
+
f.given( 0 ) { |x| 0 }
|
6
|
+
f.given( 1 ) { |x| 1 }
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "Dispatch on a functor object should" do
|
data/test/functor.rb
CHANGED
@@ -6,23 +6,25 @@ class Repeater
|
|
6
6
|
functor( :repeat, Integer ) { |x| x * @times }
|
7
7
|
functor( :repeat, String ) { |s| [].fill( s, 0, @times ).join(' ') }
|
8
8
|
functor( :repeat ) { nil }
|
9
|
+
functor( :distraction, Integer ) { |x| "Boo!" }
|
9
10
|
end
|
10
11
|
|
11
12
|
describe "Dispatch on instance method should" do
|
12
13
|
|
13
14
|
before do
|
14
15
|
@r = Repeater.new
|
15
|
-
@r.times =
|
16
|
+
@r.times = 3
|
16
17
|
end
|
17
18
|
|
18
19
|
specify "invoke different methods with object scope based on arguments" do
|
19
|
-
@r.
|
20
|
-
@r.repeat(
|
20
|
+
@r.distraction( 5 )
|
21
|
+
@r.repeat( 5 ).should == 15
|
22
|
+
@r.repeat( "-" ).should == '- - -'
|
21
23
|
@r.repeat.should == nil
|
22
24
|
end
|
23
25
|
|
24
26
|
specify "raise an exception if there is no matching value" do
|
25
|
-
lambda { @r.repeat( 7.3 ) }.should.raise(
|
27
|
+
lambda { @r.repeat( 7.3 ) }.should.raise(Functor::NoMatch)
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
data/test/guards.rb
CHANGED
@@ -4,8 +4,8 @@ describe "Dispatch should support guards" do
|
|
4
4
|
|
5
5
|
before do
|
6
6
|
@stripe = Functor.new do |f|
|
7
|
-
f.given( lambda { |x| x % 2 == 1 } ) { 'silver' }
|
8
|
-
f.given( lambda { |x| x % 2 == 0 } ) { 'white' }
|
7
|
+
f.given( lambda { |x| x % 2 == 1 } ) { |x| 'silver' }
|
8
|
+
f.given( lambda { |x| x % 2 == 0 } ) { |x| 'white' }
|
9
9
|
end
|
10
10
|
|
11
11
|
@safe_divide = Functor.new do |f|
|
data/test/inheritance.rb
CHANGED
@@ -1,24 +1,29 @@
|
|
1
1
|
require "#{File.dirname(__FILE__)}/helpers"
|
2
2
|
|
3
|
-
class
|
3
|
+
class Parent
|
4
4
|
include Functor::Method
|
5
|
-
functor( :foo, Integer ) { |x| [
|
6
|
-
functor( :foo, String ) { |s| [
|
7
|
-
functor( :foo, Float ) { |h| [
|
5
|
+
functor( :foo, Integer ) { |x| [ Parent, Integer ] }
|
6
|
+
functor( :foo, String ) { |s| [ Parent, String ] }
|
7
|
+
functor( :foo, Float ) { |h| [ Parent, Float ] }
|
8
8
|
end
|
9
9
|
|
10
|
-
class
|
11
|
-
functor( :foo, String ) { |s| [
|
10
|
+
class Child < Parent
|
11
|
+
functor( :foo, String ) { |s| [ Child, String ] }
|
12
|
+
functor( :foo, Float ) { |x| super(x).reverse }
|
12
13
|
end
|
13
14
|
|
14
15
|
describe "Functor methods should support inheritance" do
|
15
16
|
|
16
17
|
specify "by inheriting base class implementations" do
|
17
|
-
|
18
|
+
Child.new.foo( 5 ).should == [ Parent, Integer ]
|
18
19
|
end
|
19
20
|
|
20
21
|
specify "by allowing derived classes to override an implementation" do
|
21
|
-
|
22
|
+
Child.new.foo( "bar" ).should == [ Child, String ]
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "by allowing #super" do
|
26
|
+
Child.new.foo(3.0).should == [ Float, Parent]
|
22
27
|
end
|
23
28
|
|
24
29
|
end
|
data/test/matchers.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "#{File.dirname(__FILE__)}/helpers"
|
2
2
|
|
3
|
-
class
|
3
|
+
class Matchers
|
4
4
|
include Functor::Method
|
5
5
|
functor( :foo, Integer ) { |a| "===" }
|
6
6
|
functor( :foo, 1 ) { |a| "==" }
|
@@ -10,15 +10,15 @@ end
|
|
10
10
|
describe "Functors match" do
|
11
11
|
|
12
12
|
specify "using ==" do
|
13
|
-
|
13
|
+
Matchers.new.foo( 1 ).should == "=="
|
14
14
|
end
|
15
15
|
|
16
16
|
specify "using ===" do
|
17
|
-
|
17
|
+
Matchers.new.foo( 2 ).should == "==="
|
18
18
|
end
|
19
19
|
|
20
20
|
specify "using #call" do
|
21
|
-
|
21
|
+
Matchers.new.foo( "boo" ).should == "Lambda: boo"
|
22
22
|
end
|
23
23
|
|
24
24
|
end
|