functor 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|