automatthew-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.
@@ -0,0 +1,2 @@
1
+ 0.1 - Initial implemention of Functor class.
2
+ 0.2 - Added method dispatch, to_proc support, tests.
@@ -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( Integer ) { |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.
@@ -0,0 +1,53 @@
1
+ require 'lib/object'
2
+ class Functor
3
+
4
+ module Method
5
+ def self.included( k )
6
+ def k.functor( name, *args, &block )
7
+ functors = module_eval { @__functors ||= {} }
8
+ unless functors[ name ]
9
+ functors[ name ] = Functor.new
10
+ eval <<-CODE
11
+ def #{name}( *args, &block )
12
+ begin
13
+ functors = self.class.module_eval { @__functors }
14
+ functors[ :#{name} ].bind( self ).call( *args, &block )
15
+ rescue ArgumentError => e
16
+ begin
17
+ super
18
+ rescue NoMethodError => f
19
+ raise e
20
+ end
21
+ end
22
+ end
23
+ CODE
24
+ end
25
+ functors[ name ].given( *args, &block )
26
+ end
27
+ end
28
+ end
29
+
30
+ def initialize( &block ) ; @patterns = []; instance_eval(&block) if block_given? ; end
31
+
32
+ def given( *pattern, &block ) ; @patterns.push [ pattern, block ] ; self ; end
33
+
34
+ def bind( object ) ; @object = object ; self ; end
35
+
36
+ def call( *args, &block )
37
+ args.push( block ) if block_given?
38
+ pattern, action = @patterns.find { |pattern, action| match?( args, pattern ) }
39
+ raise ArgumentError.new( "argument mismatch for argument(s): #{args.inspect}." ) unless action
40
+ @object ? @object.instance_exec( *args, &action ) : action.call( *args )
41
+ end
42
+
43
+ def to_proc
44
+ lambda { |*args| self.call(*args) }
45
+ end
46
+
47
+ private
48
+
49
+ def match?( args, pattern )
50
+ pattern.zip(args).all? { |x,y| x === y or x == y } if pattern.length == args.length
51
+ end
52
+
53
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ require 'test/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
+
@@ -0,0 +1,28 @@
1
+ require 'test/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
+ end
9
+
10
+ describe "Dispatch on instance method should" do
11
+
12
+ before do
13
+ @r = Repeater.new
14
+ @r.times = 5
15
+ end
16
+
17
+ specify "invoke different methods with object scope based on arguments" do
18
+ @r.repeat( 5 ).should == 25
19
+ @r.repeat( "-" ).should == '- - - - -'
20
+ end
21
+
22
+ specify "should raise an exception if there is no matching value" do
23
+ lambda { @r.repeat( 7.3) }.should.raise(ArgumentError)
24
+ end
25
+
26
+ end
27
+
28
+
@@ -0,0 +1,14 @@
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
+ require 'lib/functor'
@@ -0,0 +1,28 @@
1
+ require 'test/helpers'
2
+
3
+ class A
4
+ include Functor::Method
5
+ functor( :test, Integer ) { |x| "A: Integer" }
6
+ functor( :test, String ) { |s| "A: String" }
7
+ end
8
+
9
+ class B < A
10
+ functor( :test, String ) { |s| "B: String" }
11
+ end
12
+
13
+ describe "Functor methods should support inheritance" do
14
+
15
+ before do
16
+ @b = B.new
17
+ end
18
+
19
+ specify "by inheriting base class implementations" do
20
+ @b.test( 5 ).should == "A: Integer"
21
+ end
22
+
23
+ specify "by allowing derived classes to override an implementation" do
24
+ @b.test( "foo" ).should == "B: String"
25
+ end
26
+
27
+ end
28
+
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: automatthew-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: funkytor
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
+