dyoder-functor 0.2

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 ADDED
@@ -0,0 +1,2 @@
1
+ 0.1 - Initial implemention of Functor class.
2
+ 0.2 - Added method dispatch, to_proc support, tests.
data/doc/README ADDED
@@ -0,0 +1,36 @@
1
+ Functor provides pattern-based function and method dispatch for Ruby. To use it in a class:
2
+
3
+ class Repeater
4
+ attr_accessor :times
5
+ include Functor::Method
6
+ functor( :repeat, Integer ) { |x| x * @times }
7
+ functor( :repeat, String ) { |s| [].fill( s, 0..@times ).join(' ') }
8
+ end
9
+
10
+ r = Repeater.new
11
+ r.times = 5
12
+ r.repeat( 5 ) # => 25
13
+ r.repeat( "-" ) # => "- - - - -"
14
+ r.repeat( 7.3 ) # => RuntimeError!
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.
17
+
18
+ You can also define Functor objects directly:
19
+
20
+ fib = Functor.new do
21
+ given( 0 ) { 0 }
22
+ given( 1 ) { 1 }
23
+ given( 2..10000 ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
24
+ end
25
+
26
+ You can use functors directly with functions taking a block like this:
27
+
28
+ [ *0..10 ].map( &fib ) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
29
+
30
+ You can explicitly bind self using #bind:
31
+
32
+ fun.bind( obj )
33
+
34
+ which is actually how the method dispatch is implemented.
35
+
36
+ Arguments are matched first using === and then ==, so anything that supports these methods can be matched against.
data/lib/functor.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'lib/object'
2
+
3
+ class Functor
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
+ module Method
12
+ def self.included( k )
13
+ def k.functor( name, *args, &block )
14
+ functors = module_eval { @__functors ||= {} }
15
+ unless functors[ name ]
16
+ functors[ name ] = Functor.new
17
+ klass = self.name
18
+ module_eval <<-CODE
19
+ def #{name}( *args, &block )
20
+ begin
21
+ functors = #{klass}.module_eval { @__functors }
22
+ functors[ :#{name} ].bind( self ).call( *args, &block )
23
+ rescue ArgumentError => e
24
+ begin
25
+ super
26
+ rescue NoMethodError => f
27
+ raise e
28
+ end
29
+ end
30
+ end
31
+ CODE
32
+ end
33
+ functors[ name ].given( *args, &block )
34
+ end
35
+ end
36
+ end
37
+
38
+ def initialize( &block ) @patterns = {}; instance_eval( &block ) if block_given? ; end
39
+
40
+ def given( *pattern, &action ) ; @patterns[ pattern ] = action ; end
41
+
42
+ def bind( object ) ; @object = object ; self ; end
43
+
44
+ def call( *args, &block )
45
+ 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 )
50
+ end
51
+
52
+ def to_proc
53
+ lambda { |*args| self.call(*args) }
54
+ end
55
+
56
+ private
57
+
58
+ def match?( args, pattern )
59
+ pattern.zip(args).all? { |x,y| x === y or x == y or
60
+ ( x.respond_to?(:call) && x.call( y ) ) } if pattern.length == args.length
61
+ end
62
+
63
+ end
data/lib/object.rb ADDED
@@ -0,0 +1,17 @@
1
+ class Object
2
+ # This is an extremely powerful little function that will be built-in to Ruby 1.9.
3
+ # This version is from Mauricio Fernandez via ruby-talk. Works like instance_eval
4
+ # except that you can pass parameters to the block. This means you can define a block
5
+ # intended for use with instance_eval, pass it to another method, which can then
6
+ # invoke with parameters. This is used quite a bit by the Waves::Mapping code.
7
+ def instance_exec(*args, &block)
8
+ mname = "__instance_exec_#{Thread.current.object_id.abs}"
9
+ class << self; self end.class_eval{ define_method(mname, &block) }
10
+ begin
11
+ ret = send(mname, *args)
12
+ ensure
13
+ class << self; self end.class_eval{ undef_method(mname) } rescue nil
14
+ end
15
+ ret
16
+ end
17
+ end
data/test/fib.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "#{File.dirname(__FILE__)}/helpers"
2
+
3
+ fib ||= Functor.new do
4
+ given( 0 ) { 0 }
5
+ given( 1 ) { 1 }
6
+ given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
7
+ end
8
+
9
+ describe "Dispatch on a functor object should" do
10
+
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]
13
+ end
14
+
15
+ end
16
+
data/test/functor.rb ADDED
@@ -0,0 +1,29 @@
1
+ require "#{File.dirname(__FILE__)}/helpers"
2
+
3
+ class Repeater
4
+ attr_accessor :times
5
+ include Functor::Method
6
+ functor( :repeat, Integer ) { |x| x * @times }
7
+ functor( :repeat, String ) { |s| [].fill( s, 0, @times ).join(' ') }
8
+ functor( :repeat ) { nil }
9
+ end
10
+
11
+ describe "Dispatch on instance method should" do
12
+
13
+ before do
14
+ @r = Repeater.new
15
+ @r.times = 5
16
+ end
17
+
18
+ specify "invoke different methods with object scope based on arguments" do
19
+ @r.repeat( 5 ).should == 25
20
+ @r.repeat( "-" ).should == '- - - - -'
21
+ @r.repeat.should == nil
22
+ end
23
+
24
+ specify "raise an exception if there is no matching value" do
25
+ lambda { @r.repeat( 7.3) }.should.raise(ArgumentError)
26
+ end
27
+ end
28
+
29
+
data/test/helpers.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ %w{ bacon }.each { |dep| require dep }
3
+ Bacon.summary_on_exit
4
+
5
+ module Kernel
6
+ private
7
+ def specification(name, &block) Bacon::Context.new(name, &block) end
8
+ end
9
+
10
+ Bacon::Context.instance_eval do
11
+ alias_method :specify, :it
12
+ end
13
+
14
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
15
+ require "functor"
@@ -0,0 +1,31 @@
1
+ require "#{File.dirname(__FILE__)}/helpers"
2
+
3
+ class A
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" }
9
+ end
10
+
11
+ class B < A
12
+ functor( :smurf, String ) { |s| "B: String" }
13
+ functor( :smurf, Hash ) { |h| "#{super} and B: Hash" }
14
+ end
15
+
16
+ describe "Functor methods should support inheritance" do
17
+
18
+ specify "by inheriting base class implementations" do
19
+ B.new.smurf( 5 ).should == "A: Integer"
20
+ end
21
+
22
+ 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"
26
+ end
27
+
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"
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dyoder-functor
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Dan Yoder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: dan@zeraweb.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - doc/HISTORY
26
+ - doc/README
27
+ - lib/functor.rb
28
+ - lib/object.rb
29
+ - test/fib.rb
30
+ - test/functor.rb
31
+ - test/helpers.rb
32
+ - test/inheritance.rb
33
+ has_rdoc: true
34
+ homepage: http://dev.zeraweb.com/
35
+ post_install_message:
36
+ rdoc_options: []
37
+
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 1.8.6
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project: functor
55
+ rubygems_version: 1.0.1
56
+ signing_key:
57
+ specification_version: 2
58
+ summary: Pattern-based dispatch for Ruby, inspired by Topher Cyll's multi.
59
+ test_files: []
60
+