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 +2 -0
- data/doc/README +36 -0
- data/lib/functor.rb +63 -0
- data/lib/object.rb +17 -0
- data/test/fib.rb +16 -0
- data/test/functor.rb +29 -0
- data/test/helpers.rb +15 -0
- data/test/inheritance.rb +31 -0
- metadata +60 -0
data/doc/HISTORY
ADDED
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"
|
data/test/inheritance.rb
ADDED
@@ -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
|
+
|