dyoder-functor 0.2 → 0.3.1
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/doc/HISTORY +3 -1
- data/doc/README +14 -5
- data/lib/functor.rb +22 -21
- data/test/fib.rb +2 -2
- data/test/functor.rb +1 -1
- data/test/guards.rb +15 -0
- data/test/inheritance.rb +9 -12
- data/test/matchers.rb +24 -0
- data/test/reopening.rb +18 -0
- metadata +7 -2
data/doc/HISTORY
CHANGED
@@ -1,2 +1,4 @@
|
|
1
1
|
0.1 - Initial implemention of Functor class.
|
2
|
-
0.2 - Added method dispatch, to_proc support, tests.
|
2
|
+
0.2 - Added method dispatch, to_proc support, tests.
|
3
|
+
0.3 - Added support for guards and redefinition.
|
4
|
+
0.3.1 - Made thread-safe and added ability to call base class functors.
|
data/doc/README
CHANGED
@@ -11,9 +11,11 @@ Functor provides pattern-based function and method dispatch for Ruby. To use it
|
|
11
11
|
r.times = 5
|
12
12
|
r.repeat( 5 ) # => 25
|
13
13
|
r.repeat( "-" ) # => "- - - - -"
|
14
|
-
r.repeat( 7.3 ) # =>
|
14
|
+
r.repeat( 7.3 ) # => ArgumentError!
|
15
15
|
|
16
|
-
Warning: This defines a class instance variable @
|
16
|
+
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:
|
17
|
+
|
18
|
+
A.functors[ :foo ].apply( self, 'bar' )
|
17
19
|
|
18
20
|
You can also define Functor objects directly:
|
19
21
|
|
@@ -27,10 +29,17 @@ You can use functors directly with functions taking a block like this:
|
|
27
29
|
|
28
30
|
[ *0..10 ].map( &fib ) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
29
31
|
|
30
|
-
You can
|
32
|
+
You can call a functor as a method using #apply:
|
31
33
|
|
32
|
-
fun.
|
34
|
+
fun.apply( obj, 7 )
|
33
35
|
|
34
36
|
which is actually how the method dispatch is implemented.
|
35
37
|
|
36
|
-
Arguments are matched first using === and then ==, so anything that supports these methods can be matched against.
|
38
|
+
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:
|
39
|
+
|
40
|
+
stripe ||= Functor.new do
|
41
|
+
given( lambda { |x| x % 2 == 0 } ) { 'white' }
|
42
|
+
given( lambda { |x| x % 2 == 1 } ) { 'silver' }
|
43
|
+
end
|
44
|
+
|
45
|
+
which will return "white" and "silver" alternately for a sequence of numbers.
|
data/lib/functor.rb
CHANGED
@@ -2,24 +2,16 @@ require 'lib/object'
|
|
2
2
|
|
3
3
|
class Functor
|
4
4
|
|
5
|
-
def self.precedence
|
6
|
-
lambda do |pattern|
|
7
|
-
case pattern ; when Class then 0 ; when Proc then 1 ; when Regexp then 2 ; else 3 ; end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
5
|
module Method
|
12
6
|
def self.included( k )
|
7
|
+
def k.functors ; @__functors ||= {} ; end
|
13
8
|
def k.functor( name, *args, &block )
|
14
|
-
functors = module_eval { @__functors ||= {} }
|
15
9
|
unless functors[ name ]
|
16
10
|
functors[ name ] = Functor.new
|
17
|
-
klass = self.name
|
18
|
-
module_eval <<-CODE
|
11
|
+
klass = self.name ; module_eval <<-CODE
|
19
12
|
def #{name}( *args, &block )
|
20
13
|
begin
|
21
|
-
|
22
|
-
functors[ :#{name} ].bind( self ).call( *args, &block )
|
14
|
+
#{klass}.functors[ :#{name} ].apply( self, *args, &block )
|
23
15
|
rescue ArgumentError => e
|
24
16
|
begin
|
25
17
|
super
|
@@ -35,26 +27,35 @@ class Functor
|
|
35
27
|
end
|
36
28
|
end
|
37
29
|
|
38
|
-
def initialize( &block )
|
30
|
+
def initialize( &block )
|
31
|
+
@rules = [] ; instance_eval( &block ) if block_given?
|
32
|
+
end
|
39
33
|
|
40
|
-
def given( *pattern, &action )
|
34
|
+
def given( *pattern, &action )
|
35
|
+
@rules.delete_if { |p,a| p == pattern }
|
36
|
+
@rules << [ pattern, action ]
|
37
|
+
end
|
41
38
|
|
42
|
-
def
|
39
|
+
def apply( object, *args, &block )
|
40
|
+
object.instance_exec( *args, &match( *args, &block ) )
|
41
|
+
end
|
43
42
|
|
44
43
|
def call( *args, &block )
|
45
44
|
args.push( block ) if block_given?
|
46
|
-
|
47
|
-
raise ArgumentError.new( "argument mismatch for argument(s): #{args.inspect}." ) if candidates.empty?
|
48
|
-
action = @patterns[ candidates.sort( &Functor.precedence ).first ]
|
49
|
-
@object ? @object.instance_exec( *args, &action ) : action.call( *args )
|
45
|
+
match( *args, &block ).call( *args )
|
50
46
|
end
|
51
47
|
|
52
|
-
def to_proc
|
53
|
-
lambda { |*args| self.call(*args) }
|
54
|
-
end
|
48
|
+
def to_proc ; lambda { |*args| self.call( *args ) } ; end
|
55
49
|
|
56
50
|
private
|
57
51
|
|
52
|
+
def match( *args, &block )
|
53
|
+
args.push( block ) if block_given?
|
54
|
+
pattern, action = @rules.find { | pattern, action | match?( args, pattern ) }
|
55
|
+
raise ArgumentError.new( "argument mismatch for argument(s): #{args.inspect}." ) unless action
|
56
|
+
return action
|
57
|
+
end
|
58
|
+
|
58
59
|
def match?( args, pattern )
|
59
60
|
pattern.zip(args).all? { |x,y| x === y or x == y or
|
60
61
|
( x.respond_to?(:call) && x.call( y ) ) } if pattern.length == args.length
|
data/test/fib.rb
CHANGED
@@ -3,13 +3,13 @@ require "#{File.dirname(__FILE__)}/helpers"
|
|
3
3
|
fib ||= Functor.new do
|
4
4
|
given( 0 ) { 0 }
|
5
5
|
given( 1 ) { 1 }
|
6
|
-
given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
|
6
|
+
given( Integer ) { | n | self.call( n - 1 ) + self.call( n - 2 ) }
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "Dispatch on a functor object should" do
|
10
10
|
|
11
11
|
specify "be able to implement the Fibonacci function" do
|
12
|
-
[*0..10].map( &fib ).should == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
12
|
+
[*0..10].map( &fib ).should == [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ]
|
13
13
|
end
|
14
14
|
|
15
15
|
end
|
data/test/functor.rb
CHANGED
data/test/guards.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test/helpers'
|
2
|
+
|
3
|
+
stripe ||= Functor.new do
|
4
|
+
given( lambda { |x| x % 2 == 0 } ) { 'white' }
|
5
|
+
given( lambda { |x| x % 2 == 1 } ) { 'silver' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Dipatch should support guards" do
|
9
|
+
|
10
|
+
specify "allowing you to use odd or even numbers as a dispatcher" do
|
11
|
+
[*0..9].map( &stripe ).should == %w( white silver ) * 5
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
data/test/inheritance.rb
CHANGED
@@ -2,30 +2,27 @@ require "#{File.dirname(__FILE__)}/helpers"
|
|
2
2
|
|
3
3
|
class A
|
4
4
|
include Functor::Method
|
5
|
-
functor( :
|
6
|
-
functor( :
|
7
|
-
functor( :
|
8
|
-
functor( :smurf, Hash ) { |h| "A: Hash" }
|
5
|
+
functor( :foo, Integer ) { |x| [ A, Integer ] }
|
6
|
+
functor( :foo, String ) { |s| [ A, String ] }
|
7
|
+
functor( :foo, Float ) { |h| [ A, Float ] }
|
9
8
|
end
|
10
9
|
|
11
10
|
class B < A
|
12
|
-
functor( :
|
13
|
-
functor( :
|
11
|
+
functor( :foo, String ) { |s| [ B, String ] }
|
12
|
+
functor( :foo, Float ) { |f| [ B, *A.functors[:foo].apply( self, f ) ] }
|
14
13
|
end
|
15
14
|
|
16
15
|
describe "Functor methods should support inheritance" do
|
17
16
|
|
18
17
|
specify "by inheriting base class implementations" do
|
19
|
-
B.new.
|
18
|
+
B.new.foo( 5 ).should == [ A, Integer ]
|
20
19
|
end
|
21
20
|
|
22
21
|
specify "by allowing derived classes to override an implementation" do
|
23
|
-
B.new.
|
24
|
-
A.new.smurf( :foo ).should == "A: String"
|
25
|
-
B.new.smurf( :foo ).should == "B: String"
|
22
|
+
B.new.foo( "bar" ).should == [ B, String ]
|
26
23
|
end
|
27
24
|
|
28
|
-
specify "by allowing to call
|
29
|
-
B.new.
|
25
|
+
specify "by allowing you to call base class functors using #functors" do
|
26
|
+
B.new.foo( 1.0 ).should == [ B, A, Float ]
|
30
27
|
end
|
31
28
|
end
|
data/test/matchers.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
class C
|
4
|
+
include Functor::Method
|
5
|
+
functor( :foo, 1 ) { |a| "==" }
|
6
|
+
functor( :foo, Integer ) { |a| "===" }
|
7
|
+
functor( :foo, lambda { |a| a == "boo" } ) { |v| "Lambda: #{v}" }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Functors match" do
|
11
|
+
|
12
|
+
specify "using ==" do
|
13
|
+
C.new.foo( 1 ).should == "=="
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "using ===" do
|
17
|
+
C.new.foo( 2 ).should == "==="
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "using #call" do
|
21
|
+
C.new.foo( "boo" ).should == "Lambda: boo"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/test/reopening.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
class A
|
4
|
+
include Functor::Method
|
5
|
+
functor( :foo, Integer ) { |x| 1 }
|
6
|
+
end
|
7
|
+
|
8
|
+
class A
|
9
|
+
functor( :foo, Integer ) { |x| 2 }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Functor methods should support reopening" do
|
13
|
+
|
14
|
+
specify "by allowing reopening of a class to override an implementation" do
|
15
|
+
A.new.foo( 5 ).should == 2
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dyoder-functor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Yoder
|
8
|
+
- Matthew King
|
9
|
+
- Lawrence Pit
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
13
|
|
12
|
-
date: 2008-06-
|
14
|
+
date: 2008-06-15 00:00:00 -07:00
|
13
15
|
default_executable:
|
14
16
|
dependencies: []
|
15
17
|
|
@@ -28,8 +30,11 @@ files:
|
|
28
30
|
- lib/object.rb
|
29
31
|
- test/fib.rb
|
30
32
|
- test/functor.rb
|
33
|
+
- test/guards.rb
|
31
34
|
- test/helpers.rb
|
32
35
|
- test/inheritance.rb
|
36
|
+
- test/matchers.rb
|
37
|
+
- test/reopening.rb
|
33
38
|
has_rdoc: true
|
34
39
|
homepage: http://dev.zeraweb.com/
|
35
40
|
post_install_message:
|