functional-ruby 0.5.0

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,5 @@
1
+ require 'functional/behavior'
2
+ require 'functional/concurrency'
3
+ require 'functional/core'
4
+ require 'functional/pattern_matching'
5
+ require 'functional/version'
@@ -0,0 +1,59 @@
1
+ def behavior_info(name, callbacks = {})
2
+ $__behavior_info__ ||= {}
3
+ $__behavior_info__[name.to_sym] = callbacks.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
4
+ end
5
+
6
+ alias :behaviour_info :behavior_info
7
+ alias :interface :behavior_info
8
+
9
+ class Object
10
+ def behaves_as?(name)
11
+
12
+ name = name.to_sym
13
+ bi = $__behavior_info__[name]
14
+ return false if bi.nil?
15
+
16
+ bi.each do |method, arity|
17
+ begin
18
+ return false unless arity == :any || self.method(method).arity == arity
19
+ rescue NameError
20
+ return false
21
+ end
22
+ end
23
+
24
+ return true
25
+ end
26
+ end
27
+
28
+ def behavior(name)
29
+
30
+ name = name.to_sym
31
+ raise ArgumentError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
32
+
33
+ clazz = self.method(:behavior).receiver
34
+
35
+ unless clazz.instance_methods(false).include?(:behaviors)
36
+ class << clazz
37
+ def behaviors
38
+ @behaviors ||= []
39
+ end
40
+ end
41
+ end
42
+
43
+ clazz.behaviors << name
44
+
45
+ class << clazz
46
+ def new(*args, &block)
47
+ name = self.behaviors.first
48
+ obj = super
49
+ unless obj.behaves_as?(name)
50
+ raise ArgumentError.new("undefined callback functions in #{self} (behavior '#{name}')")
51
+ else
52
+ return obj
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ alias :behaviour :behavior
59
+ alias :behaves_as :behavior
@@ -0,0 +1,2 @@
1
+ # Support the Scandinavian spelling
2
+ require_relative 'behavior'
@@ -0,0 +1,27 @@
1
+ # http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
2
+ # http://www.lesismore.co.za/rubyenums.html
3
+ # http://gistflow.com/posts/682-ruby-enums-approaches
4
+
5
+ # http://richhickey.github.io/clojure/clojure.core-api.html
6
+ # * agent
7
+ # * add-watch
8
+ # * apply
9
+ # * assert
10
+ # * await
11
+ # * future
12
+ # * memoize
13
+ # * promise
14
+ # * send
15
+ # * slurp
16
+
17
+ # Other stuff
18
+ # * pure - creates an object, freezes it, and removes the unfreeze method
19
+ # * retry - retries something x times if it fails
20
+ # * promise = ala JavaScript http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
21
+ # * ada range type - http://en.wikibooks.org/wiki/Ada_Programming/Types/range
22
+ # * slurpee - slurp + erb parsing
23
+ # * spawn/send/receive - http://www.erlang.org/doc/reference_manual/processes.html
24
+
25
+ # Using Ruby's queue for sending messages between threads
26
+ # * http://www.subelsky.com/2010/02/using-rubys-queue-class-to-manage-inter.html
27
+ # * http://www.ruby-doc.org/stdlib-1.9.3/libdoc/thread/rdoc/Queue.html#method-i-pop
@@ -0,0 +1,62 @@
1
+ require 'pp'
2
+ require 'stringio'
3
+
4
+ Infinity = 1/0.0 unless defined?(Infinity)
5
+ NaN = 0/0.0 unless defined?(NaN)
6
+
7
+ module Kernel
8
+
9
+ private
10
+
11
+ def repl?
12
+ return ($0 == 'irb' || $0 == 'pry' || $0 == 'script/rails' || !!($0 =~ /bin\/bundle$/))
13
+ end
14
+ module_function :repl?
15
+
16
+ def safe(*args, &block)
17
+ raise ArgumentError.new('no block given') unless block_given?
18
+ result = nil
19
+ t = Thread.new do
20
+ $SAFE = 3
21
+ result = self.instance_exec(*args, &block)
22
+ end
23
+ t.join
24
+ return result
25
+ end
26
+ module_function :safe
27
+
28
+ # http://rhaseventh.blogspot.com/2008/07/ruby-and-rails-how-to-get-pp-pretty.html
29
+ def pp_s(*objs)
30
+ s = StringIO.new
31
+ objs.each {|obj|
32
+ PP.pp(obj, s)
33
+ }
34
+ s.rewind
35
+ s.read
36
+ end
37
+ module_function :pp_s
38
+
39
+ # Compute the difference (delta) between two values.
40
+ #
41
+ # When a block is given the block will be applied to both arguments.
42
+ # Using a block in this way allows computation against a specific field
43
+ # in a data set of hashes or objects.
44
+ #
45
+ # @yield iterates over each element in the data set
46
+ # @yieldparam item each element in the data set
47
+ #
48
+ # @param [Object] v1 the first value
49
+ # @param [Object] v2 the second value
50
+ #
51
+ # @return [Float] positive value representing the difference
52
+ # between the two parameters
53
+ def delta(v1, v2)
54
+ if block_given?
55
+ v1 = yield(v1)
56
+ v2 = yield(v2)
57
+ end
58
+ return (v1 - v2).abs
59
+ end
60
+ module_function :delta
61
+
62
+ end
@@ -0,0 +1,133 @@
1
+ module PatternMatching
2
+
3
+ UNBOUND = Class.new
4
+ ALL = Class.new
5
+
6
+ private
7
+
8
+ GUARD_CLAUSE = Class.new do # :nodoc:
9
+ def initialize(func, clazz, matcher) # :nodoc:
10
+ @func = func
11
+ @clazz = clazz
12
+ @matcher = matcher
13
+ end
14
+ def when(&block) # :nodoc:
15
+ unless block_given?
16
+ raise ArgumentError.new("block missing for `when` guard on function `#{@func}` of class #{@clazz}")
17
+ end
18
+ @matcher[@matcher.length-1] = block
19
+ return nil
20
+ end
21
+ end
22
+
23
+ def self.__match_pattern__(args, pattern) # :nodoc:
24
+ return unless (pattern.last == ALL && args.length >= pattern.length) \
25
+ || (args.length == pattern.length)
26
+ pattern.each_with_index do |p, i|
27
+ break if p == ALL && i+1 == pattern.length
28
+ arg = args[i]
29
+ next if p.is_a?(Class) && arg.is_a?(p)
30
+ if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
31
+ p.each do |key, value|
32
+ return false unless arg.has_key?(key)
33
+ next if value == UNBOUND
34
+ return false unless arg[key] == value
35
+ end
36
+ next
37
+ end
38
+ return false unless p == UNBOUND || p == arg
39
+ end
40
+ return true
41
+ end
42
+
43
+ def self.__unbound_args__(match, args) # :nodoc:
44
+ argv = []
45
+ match.first.each_with_index do |p, i|
46
+ if p == ALL && i == match.first.length-1
47
+ argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
48
+ elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
49
+ p.each do |key, value|
50
+ argv << args[i][key] if value == UNBOUND
51
+ end
52
+ elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
53
+ argv << args[i]
54
+ end
55
+ end
56
+ return argv
57
+ end
58
+
59
+ def self.__pattern_match__(clazz, func, *args, &block) # :nodoc:
60
+ args = args.first
61
+
62
+ matchers = clazz.__function_pattern_matches__[func]
63
+ return [:nodef, nil] if matchers.nil?
64
+
65
+ match = matchers.detect do |matcher|
66
+ if PatternMatching.__match_pattern__(args, matcher.first)
67
+ if matcher.last.nil?
68
+ true # no guard clause
69
+ else
70
+ self.instance_exec(*PatternMatching.__unbound_args__(matcher, args), &matcher.last)
71
+ end
72
+ end
73
+ end
74
+
75
+ return (match ? [:ok, match] : [:nomatch, nil])
76
+ end
77
+
78
+ protected
79
+
80
+ def self.included(base)
81
+
82
+ class << base
83
+
84
+ public
85
+
86
+ def _() # :nodoc:
87
+ return UNBOUND
88
+ end
89
+
90
+ def defn(func, *args, &block)
91
+ unless block_given?
92
+ raise ArgumentError.new("block missing for definition of function `#{func}` on class #{self}")
93
+ end
94
+
95
+ pattern = __add_pattern_for__(func, *args, &block)
96
+
97
+ unless self.instance_methods(false).include?(func)
98
+
99
+ define_method(func) do |*args, &block|
100
+ result, match = PatternMatching.__pattern_match__(self.method(func).owner, func, args, block)
101
+ if result == :ok
102
+ # if a match is found call the block
103
+ argv = PatternMatching.__unbound_args__(match, args)
104
+ return self.instance_exec(*argv, &match[1])
105
+ else # if result == :nodef || result == :nomatch
106
+ begin
107
+ super(*args, &block)
108
+ rescue NoMethodError, ArgumentError
109
+ raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ return GUARD_CLAUSE.new(func, self, pattern)
116
+ end
117
+
118
+ public
119
+
120
+ def __function_pattern_matches__ # :nodoc:
121
+ @__function_pattern_matches__ ||= Hash.new
122
+ end
123
+
124
+ def __add_pattern_for__(func, *args, &block) # :nodoc:
125
+ block = Proc.new{} unless block_given?
126
+ matchers = self.__function_pattern_matches__
127
+ matchers[func] = [] unless matchers.has_key?(func)
128
+ matchers[func] << [args, block, nil]
129
+ return matchers[func].last
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module Functional
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+
3
+ #behaviour_info(:gen_foo, foo: 0, bar: 1, baz: 2, boom: -1, bam: :any)
4
+
5
+ #class Foo
6
+ #behavior(:gen_foo)
7
+
8
+ #def foo
9
+ #return 'foo/0'
10
+ #end
11
+
12
+ #def bar(one, &block)
13
+ #return 'bar/1'
14
+ #end
15
+
16
+ #def baz(one, two)
17
+ #return 'baz/2'
18
+ #end
19
+
20
+ #def boom(*args)
21
+ #return 'boom/-1'
22
+ #end
23
+
24
+ #def bam
25
+ #return 'bam!'
26
+ #end
27
+ #end
28
+
29
+ describe 'behavior/interface definitions' do
30
+
31
+ before(:each) do
32
+ $__behavior_info__ = {}
33
+ end
34
+
35
+ context 'behavior_info/2' do
36
+
37
+ it 'accepts a symbol name' do
38
+ behavior_info(:gen_foo, foo: 0)
39
+ $__behavior_info__.keys.first.should eq :gen_foo
40
+ end
41
+
42
+ it 'accepts a string name' do
43
+ behavior_info('gen_foo', foo: 0)
44
+ $__behavior_info__.keys.first.should eq :gen_foo
45
+ end
46
+
47
+ it 'accepts zero function names' do
48
+ behavior_info(:gen_foo)
49
+ $__behavior_info__.keys.first.should eq :gen_foo
50
+ end
51
+
52
+ it 'accepts symbols for function names' do
53
+ behavior_info(:gen_foo, foo: 0)
54
+ $__behavior_info__.values.first.should == {foo: 0}
55
+ end
56
+
57
+ it 'accepts strings as function names' do
58
+ behavior_info(:gen_foo, 'foo' => 0)
59
+ $__behavior_info__.values.first.should == {foo: 0}
60
+ end
61
+
62
+ it 'accepts numeric arity values' do
63
+ behavior_info(:gen_foo, foo: 0)
64
+ $__behavior_info__.values.first.should == {foo: 0}
65
+ end
66
+
67
+ it 'accepts :any as an arity value' do
68
+ behavior_info(:gen_foo, foo: :any)
69
+ $__behavior_info__.values.first.should == {foo: :any}
70
+ end
71
+ end
72
+
73
+ context 'behavior/1' do
74
+
75
+ it 'raises an exception if the behavior has not been defined' do
76
+ lambda {
77
+ Class.new{
78
+ behavior(:gen_foo)
79
+ }
80
+ }.should raise_error(ArgumentError)
81
+ end
82
+
83
+ it 'can be called multiple times for one class' do
84
+ behavior_info(:gen_foo, foo: 0)
85
+ behavior_info(:gen_bar, bar: 0)
86
+
87
+ lambda {
88
+ Class.new{
89
+ behavior(:gen_foo)
90
+ behavior(:gen_bar)
91
+ }
92
+ }.should_not raise_error
93
+ end
94
+ end
95
+
96
+ context 'behavior check on object creation' do
97
+
98
+ it 'raises an exception when one or more function definitions are missing' do
99
+ behavior_info(:gen_foo, foo: 0, bar: 1)
100
+ clazz = Class.new {
101
+ behavior(:gen_foo)
102
+ def foo() nil; end
103
+ }
104
+
105
+ lambda {
106
+ clazz.new
107
+ }.should raise_error(ArgumentError)
108
+ end
109
+
110
+ it 'raises an exception when one or more functions do not have proper arity' do
111
+ behavior_info(:gen_foo, foo: 0)
112
+ clazz = Class.new {
113
+ behavior(:gen_foo)
114
+ def foo(broken) nil; end
115
+ }
116
+
117
+ lambda {
118
+ clazz.new
119
+ }.should raise_error(ArgumentError)
120
+ end
121
+
122
+ it 'accepts any arity when function arity is set to :any' do
123
+ behavior_info(:gen_foo, foo: :any)
124
+ clazz = Class.new {
125
+ behavior(:gen_foo)
126
+ def foo(first) nil; end
127
+ }
128
+
129
+ lambda {
130
+ clazz.new
131
+ }.should_not raise_error(ArgumentError)
132
+ end
133
+
134
+ it 'creates the object when function definitions match' do
135
+ behavior_info(:gen_foo, foo: 0, bar: 1)
136
+ clazz = Class.new {
137
+ behavior(:gen_foo)
138
+ def foo() nil; end
139
+ def bar(first) nil; end
140
+ }
141
+
142
+ lambda {
143
+ clazz.new
144
+ }.should_not raise_error(ArgumentError)
145
+ end
146
+ end
147
+
148
+ context '#behaves_as?' do
149
+
150
+ it 'returns true when the behavior is fully suported' do
151
+ behavior_info(:gen_foo, foo: 0, bar: 1, baz: 2)
152
+ clazz = Class.new {
153
+ def foo() nil; end
154
+ def bar(first) nil; end
155
+ def baz(first, second) nil; end
156
+ }
157
+
158
+ clazz.new.behaves_as?(:gen_foo).should be_true
159
+ end
160
+
161
+ it 'accepts any arity when function arity is set to :any' do
162
+ behavior_info(:gen_foo, foo: :any)
163
+ clazz = Class.new {
164
+ def foo(*args, &block) nil; end
165
+ }
166
+
167
+ clazz.new.behaves_as?(:gen_foo).should be_true
168
+ end
169
+
170
+ it 'returns false when the behavior is partially supported' do
171
+ behavior_info(:gen_foo, foo: 0, bar: 1, baz: 2)
172
+ clazz = Class.new {
173
+ def foo() nil; end
174
+ def bar(first) nil; end
175
+ }
176
+
177
+ clazz.new.behaves_as?(:gen_foo).should be_false
178
+ end
179
+
180
+ it 'returns false when the behavior is not supported at all' do
181
+ behavior_info(:gen_foo, foo: 0, bar: 1, baz: 2)
182
+ clazz = Class.new { }
183
+ clazz.new.behaves_as?(:gen_foo).should be_false
184
+ end
185
+
186
+ it 'returns false when the behavior does not exist' do
187
+ clazz = Class.new { }
188
+ clazz.new.behaves_as?(:gen_foo).should be_false
189
+ end
190
+
191
+ it 'accepts behavior name as a symbol' do
192
+ behavior_info(:gen_foo)
193
+ clazz = Class.new { }
194
+ clazz.new.behaves_as?(:gen_foo).should be_true
195
+ end
196
+
197
+ it 'accepts behavior name as a string' do
198
+ behavior_info(:gen_foo)
199
+ clazz = Class.new { }
200
+ clazz.new.behaves_as?('gen_foo').should be_true
201
+ end
202
+ end
203
+
204
+ context 'aliases' do
205
+
206
+ it 'aliases behaviour_info for behavior_info' do
207
+ behaviour_info(:gen_foo)
208
+ clazz = Class.new { }
209
+ clazz.new.behaves_as?(:gen_foo).should be_true
210
+ end
211
+
212
+ it 'aliases interface for behavior_info' do
213
+ interface(:gen_foo)
214
+ clazz = Class.new { }
215
+ clazz.new.behaves_as?(:gen_foo).should be_true
216
+ end
217
+
218
+ it 'aliases behaviour for behavior' do
219
+ behavior_info(:gen_foo, foo: 0)
220
+ clazz = Class.new {
221
+ behaviour(:gen_foo)
222
+ def foo() nil; end
223
+ }
224
+ clazz.new.behaves_as?(:gen_foo).should be_true
225
+ end
226
+
227
+ it 'aliases behaves_as for behavior' do
228
+ behavior_info(:gen_foo, foo: 0)
229
+ clazz = Class.new {
230
+ behaves_as :gen_foo
231
+ def foo() nil; end
232
+ }
233
+ clazz.new.behaves_as?(:gen_foo).should be_true
234
+ end
235
+ end
236
+
237
+ end