functional-ruby 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- Copyright (c) Jerry D'Antonio -- released under the MIT license.
2
-
3
- http://www.opensource.org/licenses/mit-license.php
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ Copyright (c) Jerry D'Antonio -- released under the MIT license.
2
+
3
+ http://www.opensource.org/licenses/mit-license.php
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,193 +1,193 @@
1
- # Functional Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby)
2
-
3
- A gem for adding Erlang, Clojure, and Go inspired functional programming tools to Ruby.
4
-
5
- *NOTE: As of version 0.7.0 the concurrency tools from this gem have been moved to the
6
- [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby) gem.*
7
-
8
- The project is hosted on the following sites:
9
-
10
- * [RubyGems project page](https://rubygems.org/gems/functional-ruby)
11
- * [Source code on GitHub](https://github.com/jdantonio/functional-ruby)
12
- * [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/functional-ruby/frames)
13
- * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/functional-ruby)
14
- * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/functional-ruby)
15
- * [Follow me on Twitter](https://twitter.com/jerrydantonio)
16
-
17
- ## Introduction
18
-
19
- Three things I love are [Ruby](http://www.ruby-lang.org/en/),
20
- [functional](https://en.wikipedia.org/wiki/Functional_programming)
21
- [programming](http://c2.com/cgi/wiki?FunctionalProgramming) and
22
- [concurrency](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dstripbooks&field-keywords=concurrent%20programming).
23
- Sadly, the first is generally not associated with the other two. First, I reject the
24
- assertion that Ruby is an object-oriented language. It's certainly object-based, since
25
- everything is an object, but entire large-scale programs can be built without ever
26
- defining a single class. Ruby is a true multi-paradigm language and easily supports
27
- many advanced functional techniques. As to concurrency, Ruby's bad reputation is
28
- well earned, but recent versions of Ruby have made significan improvements in that
29
- area. Ruby 2.0 is now a [relevant](https://blog.heroku.com/archives/2013/6/17/ruby-2-default-new-aps)
30
- platform for concurrent applications.
31
-
32
- This gem is my small and humble attempt to help Ruby reach its full potential as
33
- a highly performant, functional programming language.
34
-
35
- ### Goals
36
-
37
- My goal is to implement various functional programming patterns in Ruby. Specifically:
38
-
39
- * Stay true to the spirit of the languages providing inspiration
40
- * But implement in a way that makes sense for Ruby
41
- * Keep the semantics as idiomatic Ruby as possible
42
- * Support features that make sense in Ruby
43
- * Exclude features that don't make sense in Ruby
44
- * Keep everything small
45
- * Be as fast as reasonably possible
46
-
47
- ## Features (and Documentation)
48
-
49
- Several features from Erlang, Go, and Clojure have been implemented thus far:
50
-
51
- * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
52
- * Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
53
- * Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
54
-
55
- ### Is it any good?
56
-
57
- [Yes](http://news.ycombinator.com/item?id=3067434)
58
-
59
- ### Supported Ruby versions
60
-
61
- MRI 1.9.2, 1.9.3, and 2.0. This library is pure Ruby and has no gem dependencies. It should be
62
- fully compatible with any Ruby interpreter that is 1.9.x compliant. I simply don't know enough
63
- about JRuby, Rubinius, or the others to fully support them. I can promise good karma and
64
- attribution on this page to anyone wishing to take responsibility for verifying compaitibility
65
- with any Ruby other than MRI.
66
-
67
- ### Install
68
-
69
- ```shell
70
- gem install functional-ruby
71
- ```
72
-
73
- or add the following line to Gemfile:
74
-
75
- ```ruby
76
- gem 'functional-ruby'
77
- ```
78
-
79
- and run `bundle install` from your shell.
80
-
81
- Once you've installed the gem you must `require` it in your project:
82
-
83
- ```ruby
84
- require 'functional'
85
- ```
86
-
87
- ### Examples
88
-
89
- For complete examples, see the specific documentation linked above. Below are a
90
- few examples to whet your appetite.
91
-
92
- #### Pattern Matching (Erlang)
93
-
94
- Documentation: [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
95
-
96
- ```ruby
97
- require 'functional/pattern_matching'
98
-
99
- class Foo
100
- include PatternMatching
101
-
102
- defn(:greet, :male) {
103
- puts "Hello, sir!"
104
- }
105
-
106
- defn(:greet, :female) {
107
- puts "Hello, ma'am!"
108
- }
109
- end
110
-
111
- foo = Foo.new
112
- foo.greet(:male) #=> "Hello, sir!"
113
- foo.greet(:female) #=> "Hello, ma'am!"
114
- ```
115
-
116
- #### Behavior (Erlang)
117
-
118
- Documentation: [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
119
-
120
- ```ruby
121
- require 'functional/behavior'
122
-
123
- behaviour_info(:gen_foo, foo: 0, self_bar: 1)
124
-
125
- class Foo
126
- behavior(:gen_foo)
127
-
128
- def foo
129
- return 'foo/0'
130
- end
131
-
132
- def self.bar(one, &block)
133
- return 'bar/1'
134
- end
135
- end
136
-
137
- foo = Foo.new
138
-
139
- Foo.behaves_as? :gen_foo #=> true
140
- foo.behaves_as?(:gen_foo) #=> true
141
- foo.behaves_as?(:bogus) #=> false
142
- 'foo'.behaves_as? :gen_foo #=> false
143
- ```
144
-
145
- #### Utilities
146
-
147
- Documentation: [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
148
-
149
- ```ruby
150
- Infinity #=> Infinity
151
- NaN #=> NaN
152
-
153
- repl? #=> true when called under irb, pry, bundle console, or rails console
154
-
155
- safe(1, 2){|a, b| a + b} #=> 3
156
- safe{ eval 'puts "Hello World!"' } #=> SecurityError: Insecure operation
157
-
158
- pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
159
-
160
- delta(-1, 1) #=> 2
161
- delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
162
-
163
- # And many more!
164
- ```
165
-
166
- ## Copyright
167
-
168
- *Functional Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
169
- It is free software and may be redistributed under the terms specified in the LICENSE file.
170
-
171
- ## License
172
-
173
- Released under the MIT license.
174
-
175
- http://www.opensource.org/licenses/mit-license.php
176
-
177
- > Permission is hereby granted, free of charge, to any person obtaining a copy
178
- > of this software and associated documentation files (the "Software"), to deal
179
- > in the Software without restriction, including without limitation the rights
180
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
181
- > copies of the Software, and to permit persons to whom the Software is
182
- > furnished to do so, subject to the following conditions:
183
- >
184
- > The above copyright notice and this permission notice shall be included in
185
- > all copies or substantial portions of the Software.
186
- >
187
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
188
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
189
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
190
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
191
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
192
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
193
- > THE SOFTWARE.
1
+ # Functional Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby)
2
+
3
+ A gem for adding Erlang, Clojure, and Go inspired functional programming tools to Ruby.
4
+
5
+ *NOTE: As of version 0.7.0 the concurrency tools from this gem have been moved to the
6
+ [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby) gem.*
7
+
8
+ The project is hosted on the following sites:
9
+
10
+ * [RubyGems project page](https://rubygems.org/gems/functional-ruby)
11
+ * [Source code on GitHub](https://github.com/jdantonio/functional-ruby)
12
+ * [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/functional-ruby/frames)
13
+ * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/functional-ruby)
14
+ * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/functional-ruby)
15
+ * [Follow me on Twitter](https://twitter.com/jerrydantonio)
16
+
17
+ ## Introduction
18
+
19
+ Three things I love are [Ruby](http://www.ruby-lang.org/en/),
20
+ [functional](https://en.wikipedia.org/wiki/Functional_programming)
21
+ [programming](http://c2.com/cgi/wiki?FunctionalProgramming) and
22
+ [concurrency](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dstripbooks&field-keywords=concurrent%20programming).
23
+ Sadly, the first is generally not associated with the other two. First, I reject the
24
+ assertion that Ruby is an object-oriented language. It's certainly object-based, since
25
+ everything is an object, but entire large-scale programs can be built without ever
26
+ defining a single class. Ruby is a true multi-paradigm language and easily supports
27
+ many advanced functional techniques. As to concurrency, Ruby's bad reputation is
28
+ well earned, but recent versions of Ruby have made significan improvements in that
29
+ area. Ruby 2.0 is now a [relevant](https://blog.heroku.com/archives/2013/6/17/ruby-2-default-new-aps)
30
+ platform for concurrent applications.
31
+
32
+ This gem is my small and humble attempt to help Ruby reach its full potential as
33
+ a highly performant, functional programming language.
34
+
35
+ ### Goals
36
+
37
+ My goal is to implement various functional programming patterns in Ruby. Specifically:
38
+
39
+ * Stay true to the spirit of the languages providing inspiration
40
+ * But implement in a way that makes sense for Ruby
41
+ * Keep the semantics as idiomatic Ruby as possible
42
+ * Support features that make sense in Ruby
43
+ * Exclude features that don't make sense in Ruby
44
+ * Keep everything small
45
+ * Be as fast as reasonably possible
46
+
47
+ ## Features (and Documentation)
48
+
49
+ Several features from Erlang, Go, and Clojure have been implemented thus far:
50
+
51
+ * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
52
+ * Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
53
+ * Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
54
+
55
+ ### Is it any good?
56
+
57
+ [Yes](http://news.ycombinator.com/item?id=3067434)
58
+
59
+ ### Supported Ruby versions
60
+
61
+ MRI 1.9.2, 1.9.3, and 2.0. This library is pure Ruby and has no gem dependencies. It should be
62
+ fully compatible with any Ruby interpreter that is 1.9.x compliant. I simply don't know enough
63
+ about JRuby, Rubinius, or the others to fully support them. I can promise good karma and
64
+ attribution on this page to anyone wishing to take responsibility for verifying compaitibility
65
+ with any Ruby other than MRI.
66
+
67
+ ### Install
68
+
69
+ ```shell
70
+ gem install functional-ruby
71
+ ```
72
+
73
+ or add the following line to Gemfile:
74
+
75
+ ```ruby
76
+ gem 'functional-ruby'
77
+ ```
78
+
79
+ and run `bundle install` from your shell.
80
+
81
+ Once you've installed the gem you must `require` it in your project:
82
+
83
+ ```ruby
84
+ require 'functional'
85
+ ```
86
+
87
+ ### Examples
88
+
89
+ For complete examples, see the specific documentation linked above. Below are a
90
+ few examples to whet your appetite.
91
+
92
+ #### Pattern Matching (Erlang)
93
+
94
+ Documentation: [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
95
+
96
+ ```ruby
97
+ require 'functional/pattern_matching'
98
+
99
+ class Foo
100
+ include PatternMatching
101
+
102
+ defn(:greet, :male) {
103
+ puts "Hello, sir!"
104
+ }
105
+
106
+ defn(:greet, :female) {
107
+ puts "Hello, ma'am!"
108
+ }
109
+ end
110
+
111
+ foo = Foo.new
112
+ foo.greet(:male) #=> "Hello, sir!"
113
+ foo.greet(:female) #=> "Hello, ma'am!"
114
+ ```
115
+
116
+ #### Behavior (Erlang)
117
+
118
+ Documentation: [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
119
+
120
+ ```ruby
121
+ require 'functional/behavior'
122
+
123
+ behaviour_info(:gen_foo, foo: 0, self_bar: 1)
124
+
125
+ class Foo
126
+ behavior(:gen_foo)
127
+
128
+ def foo
129
+ return 'foo/0'
130
+ end
131
+
132
+ def self.bar(one, &block)
133
+ return 'bar/1'
134
+ end
135
+ end
136
+
137
+ foo = Foo.new
138
+
139
+ Foo.behaves_as? :gen_foo #=> true
140
+ foo.behaves_as?(:gen_foo) #=> true
141
+ foo.behaves_as?(:bogus) #=> false
142
+ 'foo'.behaves_as? :gen_foo #=> false
143
+ ```
144
+
145
+ #### Utilities
146
+
147
+ Documentation: [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
148
+
149
+ ```ruby
150
+ Infinity #=> Infinity
151
+ NaN #=> NaN
152
+
153
+ repl? #=> true when called under irb, pry, bundle console, or rails console
154
+
155
+ safe(1, 2){|a, b| a + b} #=> 3
156
+ safe{ eval 'puts "Hello World!"' } #=> SecurityError: Insecure operation
157
+
158
+ pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
159
+
160
+ delta(-1, 1) #=> 2
161
+ delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
162
+
163
+ # And many more!
164
+ ```
165
+
166
+ ## Copyright
167
+
168
+ *Functional Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
169
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
170
+
171
+ ## License
172
+
173
+ Released under the MIT license.
174
+
175
+ http://www.opensource.org/licenses/mit-license.php
176
+
177
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
178
+ > of this software and associated documentation files (the "Software"), to deal
179
+ > in the Software without restriction, including without limitation the rights
180
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
181
+ > copies of the Software, and to permit persons to whom the Software is
182
+ > furnished to do so, subject to the following conditions:
183
+ >
184
+ > The above copyright notice and this permission notice shall be included in
185
+ > all copies or substantial portions of the Software.
186
+ >
187
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
188
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
189
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
190
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
191
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
192
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
193
+ > THE SOFTWARE.
data/lib/functional.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'functional/behavior'
2
- require 'functional/pattern_matching'
3
- require 'functional/utilities'
4
- require 'functional/version'
1
+ require 'functional/behavior'
2
+ require 'functional/pattern_matching'
3
+ require 'functional/utilities'
4
+ require 'functional/version'
@@ -1,124 +1,132 @@
1
- module Kernel
2
-
3
- BehaviorError = Class.new(StandardError)
4
-
5
- # Define a behavioral specification (interface).
6
- #
7
- # @param name [Symbol] the name of the behavior
8
- # @param functions [Hash] function names and their arity as key/value pairs
9
- def behavior_info(name, functions = {})
10
- $__behavior_info__ ||= {}
11
- $__behavior_info__[name.to_sym] = functions.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
12
- end
13
-
14
- alias :behaviour_info :behavior_info
15
- alias :interface :behavior_info
16
-
17
- module_function :behavior_info
18
- module_function :behaviour_info
19
- module_function :interface
20
-
21
- # Specify a #behavior_info to enforce on the enclosing class
22
- #
23
- # @param name [Symbol] name of the #behavior_info being implemented
24
- def behavior(name)
25
-
26
- name = name.to_sym
27
- raise BehaviorError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
28
-
29
- clazz = self.method(:behavior).receiver
30
-
31
- unless clazz.instance_methods(false).include?(:behaviors)
32
- class << clazz
33
- def behaviors
34
- @behaviors ||= []
35
- end
36
- end
37
- end
38
-
39
- clazz.behaviors << name
40
-
41
- if self.class == Module
42
- (class << self; self; end).class_eval do
43
- define_method(:included) do |base|
44
- base.class_eval do
45
- behavior(name)
46
- end
47
- end
48
- end
49
- end
50
-
51
- class << clazz
52
- def new(*args, &block)
53
- obj = super
54
- self.ancestors.each do |clazz|
55
- if clazz.respond_to?(:behaviors)
56
- clazz.behaviors.each do |behavior|
57
- unless obj.behaves_as?(behavior)
58
- raise BehaviorError.new("undefined callback functions in #{self} (behavior '#{behavior}')")
59
- end
60
- end
61
- end
62
- end
63
- return obj
64
- end
65
- end
66
- end
67
-
68
- alias :behaviour :behavior
69
- alias :behaves_as :behavior
70
-
71
- module_function :behavior
72
- module_function :behaviour
73
- module_function :behaves_as
74
- end
75
-
76
- class Object
77
-
78
- # Does the object implement the given #behavior_info?
79
- #
80
- # @note Will return true if the object implements the
81
- # required methods. The object's class hierarchy does
82
- # not necessarily have to include a corresponding
83
- # #behavior call.
84
- #
85
- # @param name [Symbol] name of the #behavior_info to
86
- # verify behavior against.
87
- #
88
- # @return [Boolean] whether or not the required public
89
- # methods are implemented
90
- def behaves_as?(name)
91
-
92
- name = name.to_sym
93
- bi = $__behavior_info__[name]
94
- return false if bi.nil?
95
-
96
- validator = proc do |obj, method, arity|
97
- (obj.respond_to?(method) && arity == :any) || obj.method(method).arity == arity
98
- end
99
-
100
- if self.is_a?(Class) || self.is_a?(Module)
101
- bi = bi.select{|method, arity| method.to_s =~ /^self_/ }
102
- end
103
-
104
- bi.each do |method, arity|
105
- begin
106
- method = method.to_s
107
- obj = self
108
-
109
- if (self.is_a?(Class) || self.is_a?(Module)) && method =~ /^self_/
110
- method = method.gsub(/^self_/, '')
111
- elsif method =~ /^self_/
112
- method = method.gsub(/^self_/, '')
113
- obj = self.class
114
- end
115
-
116
- return false unless validator.call(obj, method, arity)
117
- rescue NameError
118
- return false
119
- end
120
- end
121
-
122
- return true
123
- end
124
- end
1
+ module Kernel
2
+
3
+ BehaviorError = Class.new(StandardError)
4
+
5
+ # Define a behavioral specification (interface).
6
+ #
7
+ # @param name [Symbol] the name of the behavior
8
+ # @param functions [Hash] function names and their arity as key/value pairs
9
+ def behavior_info(name, functions = {})
10
+ $__behavior_info__ ||= {}
11
+ $__behavior_info__[name.to_sym] = functions.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
12
+ end
13
+
14
+ alias :behaviour_info :behavior_info
15
+ alias :interface :behavior_info
16
+
17
+ module_function :behavior_info
18
+ module_function :behaviour_info
19
+ module_function :interface
20
+
21
+ # Specify a #behavior_info to enforce on the enclosing class
22
+ #
23
+ # @param name [Symbol] name of the #behavior_info being implemented
24
+ def behavior(name)
25
+
26
+ name = name.to_sym
27
+ raise BehaviorError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
28
+
29
+ clazz = self.method(:behavior).receiver
30
+
31
+ unless clazz.instance_methods(false).include?(:behaviors)
32
+ class << clazz
33
+ def behaviors
34
+ @behaviors ||= []
35
+ end
36
+ end
37
+ end
38
+
39
+ clazz.behaviors << name
40
+
41
+ if self.class == Module
42
+ (class << self; self; end).class_eval do
43
+ define_method(:included) do |base|
44
+ base.class_eval do
45
+ behavior(name)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ if self.class == Class
52
+ unless self.respond_to?(:__new)
53
+ class << clazz
54
+ alias_method(:__new, :new)
55
+ end
56
+ end
57
+ end
58
+
59
+ class << clazz
60
+ def new(*args, &block)
61
+ obj = __new(*args, &block)
62
+ self.ancestors.each do |clazz|
63
+ if clazz.respond_to?(:behaviors)
64
+ clazz.behaviors.each do |behavior|
65
+ unless obj.behaves_as?(behavior)
66
+ raise BehaviorError.new("undefined callback functions in #{self} (behavior '#{behavior}')")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ return obj
72
+ end
73
+ end
74
+ end
75
+
76
+ alias :behaviour :behavior
77
+ alias :behaves_as :behavior
78
+
79
+ module_function :behavior
80
+ module_function :behaviour
81
+ module_function :behaves_as
82
+ end
83
+
84
+ class Object
85
+
86
+ # Does the object implement the given #behavior_info?
87
+ #
88
+ # @note Will return true if the object implements the
89
+ # required methods. The object's class hierarchy does
90
+ # not necessarily have to include a corresponding
91
+ # #behavior call.
92
+ #
93
+ # @param name [Symbol] name of the #behavior_info to
94
+ # verify behavior against.
95
+ #
96
+ # @return [Boolean] whether or not the required public
97
+ # methods are implemented
98
+ def behaves_as?(name)
99
+
100
+ name = name.to_sym
101
+ bi = $__behavior_info__[name]
102
+ return false if bi.nil?
103
+
104
+ validator = proc do |obj, method, arity|
105
+ (obj.respond_to?(method) && arity == :any) || obj.method(method).arity == arity
106
+ end
107
+
108
+ if self.is_a?(Class) || self.is_a?(Module)
109
+ bi = bi.select{|method, arity| method.to_s =~ /^self_/ }
110
+ end
111
+
112
+ bi.each do |method, arity|
113
+ begin
114
+ method = method.to_s
115
+ obj = self
116
+
117
+ if (self.is_a?(Class) || self.is_a?(Module)) && method =~ /^self_/
118
+ method = method.gsub(/^self_/, '')
119
+ elsif method =~ /^self_/
120
+ method = method.gsub(/^self_/, '')
121
+ obj = self.class
122
+ end
123
+
124
+ return false unless validator.call(obj, method, arity)
125
+ rescue NameError
126
+ return false
127
+ end
128
+ end
129
+
130
+ return true
131
+ end
132
+ end