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 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 ) # => RuntimeError!
14
+ r.repeat( 7.3 ) # => ArgumentError!
15
15
 
16
- Warning: This defines a class instance variable @functors behind the scenes as a side-effect. Also, functors won't inherit properly at this point.
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 explicitly bind self using #bind:
32
+ You can call a functor as a method using #apply:
31
33
 
32
- fun.bind( obj )
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
- functors = #{klass}.module_eval { @__functors }
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 ) @patterns = {}; instance_eval( &block ) if block_given? ; end
30
+ def initialize( &block )
31
+ @rules = [] ; instance_eval( &block ) if block_given?
32
+ end
39
33
 
40
- def given( *pattern, &action ) ; @patterns[ pattern ] = action ; end
34
+ def given( *pattern, &action )
35
+ @rules.delete_if { |p,a| p == pattern }
36
+ @rules << [ pattern, action ]
37
+ end
41
38
 
42
- def bind( object ) ; @object = object ; self ; end
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
- candidates = @patterns.keys.select { |pattern| match?( args, pattern ) }
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
@@ -22,7 +22,7 @@ describe "Dispatch on instance method should" do
22
22
  end
23
23
 
24
24
  specify "raise an exception if there is no matching value" do
25
- lambda { @r.repeat( 7.3) }.should.raise(ArgumentError)
25
+ lambda { @r.repeat( 7.3 ) }.should.raise(ArgumentError)
26
26
  end
27
27
  end
28
28
 
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( :smurf, Integer ) { |x| "A: Integer" }
6
- functor( :smurf, String ) { |s| "A: String" }
7
- functor( :smurf, Symbol ) { |s| smurf( "boo" ) }
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( :smurf, String ) { |s| "B: String" }
13
- functor( :smurf, Hash ) { |h| "#{super} and B: Hash" }
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.smurf( 5 ).should == "A: Integer"
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.smurf( "foo" ).should == "B: String"
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 the implementation of an overriden method on super" do
29
- B.new.smurf( {} ).should == "A: Hash and B: Hash"
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: "0.2"
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-09 00:00:00 -07:00
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: