functor 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 ADDED
@@ -0,0 +1,3 @@
1
+ 0.1 - Initial implemention of Functor class.
2
+ 0.2 - Added method dispatch, to_proc support, tests.
3
+ 0.3 - Added support for guards and redefinition.
data/doc/README ADDED
@@ -0,0 +1,45 @@
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, 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' )
19
+
20
+ You can also define Functor objects directly:
21
+
22
+ fib = Functor.new do
23
+ given( 0 ) { 0 }
24
+ given( 1 ) { 1 }
25
+ given( 2..10000 ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
26
+ end
27
+
28
+ You can use functors directly with functions taking a block like this:
29
+
30
+ [ *0..10 ].map( &fib ) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
31
+
32
+ You can explicitly bind self using #bind:
33
+
34
+ fun.bind( obj )
35
+
36
+ which is actually how the method dispatch is implemented.
37
+
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 ADDED
@@ -0,0 +1,64 @@
1
+ require 'lib/object'
2
+
3
+ class Functor
4
+
5
+ module Method
6
+ def self.included( k )
7
+ def k.functors ; @__functors ||= {} ; end
8
+ def k.functor( name, *args, &block )
9
+ unless functors[ name ]
10
+ functors[ name ] = Functor.new
11
+ klass = self.name ; module_eval <<-CODE
12
+ def #{name}( *args, &block )
13
+ begin
14
+ #{klass}.functors[ :#{name} ].apply( self, *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 )
31
+ @rules = [] ; instance_eval( &block ) if block_given?
32
+ end
33
+
34
+ def given( *pattern, &action )
35
+ @rules.delete_if { |p,a| p == pattern }
36
+ @rules << [ pattern, action ]
37
+ end
38
+
39
+ def apply( object, *args, &block )
40
+ object.instance_exec( *args, &match( *args, &block ) )
41
+ end
42
+
43
+ def call( *args, &block )
44
+ args.push( block ) if block_given?
45
+ match( *args, &block ).call( *args )
46
+ end
47
+
48
+ def to_proc ; lambda { |*args| self.call( *args ) } ; end
49
+
50
+ private
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
+
59
+ def match?( args, pattern )
60
+ pattern.zip(args).all? { |x,y| x === y or x == y or
61
+ ( x.respond_to?(:call) && x.call( y ) ) } if pattern.length == args.length
62
+ end
63
+
64
+ 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/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/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,28 @@
1
+ require "#{File.dirname(__FILE__)}/helpers"
2
+
3
+ class A
4
+ include Functor::Method
5
+ functor( :foo, Integer ) { |x| [ A, Integer ] }
6
+ functor( :foo, String ) { |s| [ A, String ] }
7
+ functor( :foo, Float ) { |h| [ A, Float ] }
8
+ end
9
+
10
+ class B < A
11
+ functor( :foo, String ) { |s| [ B, String ] }
12
+ functor( :foo, Float ) { |f| [ B, *A.functors[:foo].apply( self, f ) ] }
13
+ end
14
+
15
+ describe "Functor methods should support inheritance" do
16
+
17
+ specify "by inheriting base class implementations" do
18
+ B.new.foo( 5 ).should == [ A, Integer ]
19
+ end
20
+
21
+ specify "by allowing derived classes to override an implementation" do
22
+ B.new.foo( "bar" ).should == [ B, String ]
23
+ end
24
+
25
+ specify "by allowing you to call base class functors using #functors" do
26
+ B.new.foo( 1.0 ).should == [ B, A, Float ]
27
+ end
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 ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: functor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Dan Yoder
8
+ - Matthew King
9
+ - Lawrence Pit
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2008-06-15 00:00:00 -07:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description:
19
+ email: dan@zeraweb.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - doc/HISTORY
28
+ - doc/README
29
+ - lib/functor.rb
30
+ - lib/object.rb
31
+ - test/fib.rb
32
+ - test/functor.rb
33
+ - test/guards.rb
34
+ - test/helpers.rb
35
+ - test/inheritance.rb
36
+ - test/matchers.rb
37
+ - test/reopening.rb
38
+ has_rdoc: true
39
+ homepage: http://dev.zeraweb.com/
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.8.6
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: functor
60
+ rubygems_version: 1.0.1
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Pattern-based dispatch for Ruby, inspired by Topher Cyll's multi.
64
+ test_files: []
65
+