automatthew-functor 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+