functional-ruby 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ require 'thread'
2
+
3
+ module Functional
4
+
5
+ # An exception raised when an attempt is made to modify an
6
+ # immutable object or attribute.
7
+ FinalityError = Class.new(StandardError)
8
+
9
+ # A thread safe object that holds a single value and is "final" (meaning
10
+ # that the value can be set at most once after which it becomes immutable).
11
+ # The value can be set at instantiation which will result in the object
12
+ # becoming fully and immediately immutable. Attempting to set the value
13
+ # once it has been set is a logical error and will result in an exception
14
+ # being raised.
15
+ #
16
+ # @example Instanciation With No Value
17
+ # f = Functional::FinalVar.new
18
+ # #=> #<Functional::FinalVar unset>
19
+ # f.set? #=> false
20
+ # f.value #=> nil
21
+ # f.value = 42 #=> 42
22
+ # f.inspect
23
+ # #=> "#<Functional::FinalVar value=42>"
24
+ # f.set? #=> true
25
+ # f.value #=> 42
26
+ #
27
+ # @example Instanciation With an Initial Value
28
+ # f = Functional::FinalVar.new(42)
29
+ # #=> #<Functional::FinalVar value=42>
30
+ # f.set? #=> true
31
+ # f.value #=> 42
32
+ #
33
+ # @since 1.1.0
34
+ #
35
+ # @see Functional::FinalStruct
36
+ # @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword
37
+ #
38
+ # @!macro thread_safe_final_object
39
+ class FinalVar
40
+
41
+ # @!visibility private
42
+ NO_VALUE = Object.new.freeze
43
+
44
+ # Create a new `FinalVar` with the given value or "unset" when
45
+ # no value is given.
46
+ #
47
+ # @param [Object] value if given, the immutable value of the object
48
+ def initialize(value = NO_VALUE)
49
+ @mutex = Mutex.new
50
+ @value = value
51
+ end
52
+
53
+ # Get the current value or nil if unset.
54
+ #
55
+ # @return [Object] the current value or nil
56
+ def get
57
+ @mutex.synchronize {
58
+ has_been_set? ? @value : nil
59
+ }
60
+ end
61
+ alias_method :value, :get
62
+
63
+ # Set the value. Will raise an exception if already set.
64
+ #
65
+ # @param [Object] value the value to set
66
+ # @return [Object] the new value
67
+ # @raise [Functional::FinalityError] if the value has already been set
68
+ def set(value)
69
+ @mutex.synchronize {
70
+ if has_been_set?
71
+ raise FinalityError.new('value has already been set')
72
+ else
73
+ @value = value
74
+ end
75
+ }
76
+ end
77
+ alias_method :value=, :set
78
+
79
+ # Has the value been set?
80
+ #
81
+ # @return [Boolean] true when the value has been set else false
82
+ def set?
83
+ @mutex.synchronize {
84
+ has_been_set?
85
+ }
86
+ end
87
+ alias_method :value?, :set?
88
+
89
+ # Get the value if it has been set else set the value.
90
+ #
91
+ # @param [Object] value the value to set
92
+ # @return [Object] the current value if already set else the new value
93
+ def get_or_set(value)
94
+ @mutex.synchronize {
95
+ if has_been_set?
96
+ @value
97
+ else
98
+ @value = value
99
+ end
100
+ }
101
+ end
102
+
103
+ # Get the value if set else return the given default value.
104
+ #
105
+ # @param [Object] default the value to return if currently unset
106
+ # @return [Object] the current value when set else the given default
107
+ def fetch(default)
108
+ @mutex.synchronize {
109
+ has_been_set? ? @value : default
110
+ }
111
+ end
112
+
113
+ # Compares this object and other for equality. A `FinalVar` that is unset
114
+ # is never equal to anything else (it represents a complete absence of value).
115
+ # When set a `FinalVar` is equal to another `FinalVar` if they have the same
116
+ # value. A `FinalVar` is equal to another object if its value is equal to
117
+ # the other object using Ruby's normal equality rules.
118
+ #
119
+ # @param [Object] other the object to compare equality to
120
+ # @return [Boolean] true if equal else false
121
+ def eql?(other)
122
+ if (val = fetch(NO_VALUE)) == NO_VALUE
123
+ false
124
+ elsif other.is_a?(FinalVar)
125
+ val == other.value
126
+ else
127
+ val == other
128
+ end
129
+ end
130
+ alias_method :==, :eql?
131
+
132
+ # Describe the contents of this object in a string.
133
+ #
134
+ # @return [String] the string representation of this object
135
+ #
136
+ # @!visibility private
137
+ def inspect
138
+ if (val = fetch(NO_VALUE)) == NO_VALUE
139
+ val = 'unset'
140
+ else
141
+ val = "value=#{val.is_a?(String) ? ('"' + val + '"') : val }"
142
+ end
143
+ "#<#{self.class} #{val}>"
144
+ end
145
+
146
+ # Describe the contents of this object in a string.
147
+ #
148
+ # @return [String] the string representation of this object
149
+ #
150
+ # @!visibility private
151
+ def to_s
152
+ value.to_s
153
+ end
154
+
155
+ private
156
+
157
+ # Checks the set status without locking the mutex.
158
+ # @return [Boolean] true when set else false
159
+ def has_been_set?
160
+ @value != NO_VALUE
161
+ end
162
+ end
163
+ end
@@ -15,14 +15,18 @@ module Functional
15
15
  # @note Memoized method calls are thread safe and can safely be used in concurrent systems.
16
16
  # Declaring memoization on a function is *not* thread safe and should only be done during
17
17
  # application initialization.
18
+ #
19
+ # @since 1.0.0
18
20
  module Memo
19
21
 
22
+ # @!visibility private
20
23
  def self.extended(base)
21
24
  base.extend(ClassMethods)
22
25
  base.send(:__method_memos__=, {})
23
26
  super(base)
24
27
  end
25
28
 
29
+ # @!visibility private
26
30
  def self.included(base)
27
31
  base.extend(ClassMethods)
28
32
  base.send(:__method_memos__=, {})
@@ -6,6 +6,8 @@ module Functional
6
6
  #
7
7
  # Helper functions used when pattern matching runtime arguments against
8
8
  # a method defined with the `defn` function of Functional::PatternMatching.
9
+ #
10
+ # @since 1.0.0
9
11
  module MethodSignature
10
12
  extend self
11
13
 
@@ -17,6 +17,8 @@ module Functional
17
17
  # @see Functional::AbstractStruct
18
18
  # @see http://functionaljava.googlecode.com/svn/artifacts/3.0/javadoc/index.html Functional Java
19
19
  #
20
+ # @since 1.0.0
21
+ #
20
22
  # @!macro thread_safe_immutable_object
21
23
  class Option
22
24
  include AbstractStruct
@@ -75,6 +75,7 @@ module Functional
75
75
  end
76
76
  end
77
77
 
78
+ # @!visibility private
78
79
  def self.included(base)
79
80
  base.extend(ClassMethods)
80
81
  super(base)
@@ -57,6 +57,8 @@ module Functional
57
57
  # It is a logical extension of the `respond_to?` method, but vastly more powerful.
58
58
  #
59
59
  # @!macro protocol
60
+ #
61
+ # @since 1.0.0 (formerly "behavior")
60
62
  module Protocol
61
63
 
62
64
  # The global registry of specified protocols.
@@ -4,6 +4,8 @@ module Functional
4
4
  # itself from a block. Used by {Functional#SpecifyProtocol}.
5
5
  #
6
6
  # @see Functional::Protocol
7
+ #
8
+ # @since 1.0.0
7
9
  class ProtocolInfo
8
10
 
9
11
  # The symbolic name of the protocol
@@ -63,6 +65,7 @@ module Functional
63
65
  private
64
66
 
65
67
  # Data structure for encapsulating the protocol info data.
68
+ # @!visibility private
66
69
  Info = Struct.new(:instance_methods, :class_methods, :constants)
67
70
 
68
71
  # Does the target satisfy the constants expected by this protocol?
@@ -19,6 +19,8 @@ module Functional
19
19
  # @see Functional::AbstractStruct
20
20
  # @see Functional::Union
21
21
  #
22
+ # @since 1.0.0
23
+ #
22
24
  # @!macro thread_safe_immutable_object
23
25
  module Record
24
26
  extend self
@@ -127,8 +129,8 @@ module Functional
127
129
  # @return [Functional::AbstractStruct] the record class
128
130
  def define_initializer(record)
129
131
  record.send(:define_method, :initialize) do |data = {}|
130
- restrictions = self.class.class_variable_get(:@@restrictions)
131
- data = fields.reduce({}) do |memo, field|
132
+ restrictions = record.class_variable_get(:@@restrictions)
133
+ data = record.fields.reduce({}) do |memo, field|
132
134
  memo[field] = data.fetch(field, restrictions.clone_default(field))
133
135
  memo
134
136
  end
@@ -0,0 +1,247 @@
1
+ module Functional
2
+
3
+ # A tuple is a pure functional data strcture that is similar to an array but is
4
+ # immutable and of fixed length. Tuples support many of the same operations as
5
+ # array/list/vector.
6
+ #
7
+ # @note The current implementation uses simple Ruby arrays. This is likely to be
8
+ # very inefficient for all but the smallest tuples. The more items the tuple
9
+ # contains, the less efficient it will become. A future version will use a fast,
10
+ # immutable, persistent data structure such as a finger tree or a trie.
11
+ #
12
+ # @since 1.1.0
13
+ #
14
+ # @see http://en.wikipedia.org/wiki/Tuple
15
+ # @see http://msdn.microsoft.com/en-us/library/system.tuple.aspx
16
+ # @see http://www.tutorialspoint.com/python/python_tuples.htm
17
+ # @see http://en.cppreference.com/w/cpp/utility/tuple
18
+ # @see http://docs.oracle.com/javaee/6/api/javax/persistence/Tuple.html
19
+ # @see http://www.erlang.org/doc/reference_manual/data_types.html
20
+ # @see http://www.erlang.org/doc/man/erlang.html#make_tuple-2
21
+ # @see http://en.wikibooks.org/wiki/Haskell/Lists_and_tuples#Tuples
22
+ #
23
+ # @!macro thread_safe_immutable_object
24
+ class Tuple
25
+
26
+ # Create a new tuple with the given data items in the given order.
27
+ #
28
+ # @param [Array] data the data items to insert into the new tuple
29
+ # @raise [ArgumentError] if data is not an array or does not implement `to_a`
30
+ def initialize(data = [])
31
+ raise ArgumentError.new('data is not an array') unless data.respond_to?(:to_a)
32
+ @data = data.to_a.dup.freeze
33
+ self.freeze
34
+ end
35
+
36
+ # Retrieve the item at the given index. Indices begin at zero and increment
37
+ # up, just like Ruby arrays. Negative indicies begin at -1, which represents the
38
+ # last item in the tuple, and decrement toward the first item. If the
39
+ # given index is out of range then `nil` is returned.
40
+ #
41
+ # @param [Fixnum] index the index of the item to be retrieved
42
+ # @return [Object] the item at the given index or nil when index is out of bounds
43
+ def at(index)
44
+ @data[index]
45
+ end
46
+ alias_method :nth, :at
47
+ alias_method :[], :at
48
+
49
+ # Retrieve the item at the given index or return the given default value if the
50
+ # index is out of bounds. The behavior of indicies follows the rules for the
51
+ # `at` method.
52
+ #
53
+ # @param [Fixnum] index the index of the item to be retrieved
54
+ # @param [Object] default the value to return when given an out of bounds index
55
+ # @return [Object] the item at the given index or default when index is out of bounds
56
+ #
57
+ # @see Functional::Tuple#at
58
+ def fetch(index, default)
59
+ if index >= length || -index > length
60
+ default
61
+ else
62
+ at(index)
63
+ end
64
+ end
65
+
66
+ # The number of items in the tuple.
67
+ #
68
+ # @return [Fixnum] the number of items in the tuple
69
+ def length
70
+ @data.length
71
+ end
72
+ alias_method :size, :length
73
+
74
+ # Returns a new tuple containing elements common to the two tuples, excluding any
75
+ # duplicates. The order is preserved from the original tuple.
76
+ #
77
+ # @!macro [attach] tuple_method_param_other_return_tuple
78
+ # @param [Array] other the tuple or array-like object (responds to `to_a`) to operate on
79
+ # @return [Functional::Tuple] a new tuple with the appropriate items
80
+ def intersect(other)
81
+ Tuple.new(@data & other.to_a)
82
+ end
83
+ alias_method :&, :intersect
84
+
85
+ # Returns a new tuple by joining self with other, excluding any duplicates and
86
+ # preserving the order from the original tuple.
87
+ #
88
+ # @!macro tuple_method_param_other_return_tuple
89
+ def union(other)
90
+ Tuple.new(@data | other.to_a)
91
+ end
92
+ alias_method :|, :union
93
+
94
+ # Returns a new tuple built by concatenating the two tuples
95
+ # together to produce a third tuple.
96
+ #
97
+ # @!macro tuple_method_param_other_return_tuple
98
+ def concat(other)
99
+ Tuple.new(@data + other.to_a)
100
+ end
101
+ alias_method :+, :concat
102
+
103
+ # Returns a new tuple that is a copy of the original tuple, removing any items that
104
+ # also appear in other. The order is preserved from the original tuple.
105
+ #
106
+ # @!macro tuple_method_param_other_return_tuple
107
+ def diff(other)
108
+ Tuple.new(@data - other.to_a)
109
+ end
110
+ alias_method :-, :diff
111
+
112
+ # Returns a new tuple built by concatenating the given number of copies of self.
113
+ # Returns an empty tuple when the multiple is zero.
114
+ #
115
+ # @param [Fixnum] multiple the number of times to concatenate self
116
+ # @return [Functional::Tuple] a new tuple with the appropriate items
117
+ # @raise [ArgumentError] when multiple is a negative number
118
+ def repeat(multiple)
119
+ multiple = multiple.to_i
120
+ raise ArgumentError.new('negative argument') if multiple < 0
121
+ Tuple.new(@data * multiple)
122
+ end
123
+ alias_method :*, :repeat
124
+
125
+ # Returns a new tuple by removing duplicate values in self.
126
+ #
127
+ # @return [Functional::Tuple] the new tuple with only unique items
128
+ def uniq
129
+ Tuple.new(@data.uniq)
130
+ end
131
+
132
+ # Calls the given block once for each element in self, passing that element as a parameter.
133
+ # An Enumerator is returned if no block is given.
134
+ #
135
+ # @yieldparam [Object] item the current item
136
+ # @return [Enumerable] when no block is given
137
+ def each
138
+ return enum_for(:each) unless block_given?
139
+ @data.each do |item|
140
+ yield(item)
141
+ end
142
+ end
143
+
144
+ # Calls the given block once for each element in self, passing that element
145
+ # and the current index as parameters. An Enumerator is returned if no block is given.
146
+ #
147
+ # @yieldparam [Object] item the current item
148
+ # @yieldparam [Fixnum] index the index of the current item
149
+ # @return [Enumerable] when no block is given
150
+ def each_with_index
151
+ return enum_for(:each_with_index) unless block_given?
152
+ @data.each_with_index do |item, index|
153
+ yield(item, index)
154
+ end
155
+ end
156
+
157
+ # Calls the given block once for each element in self, passing that element
158
+ # and a tuple with all the remaining items in the tuple. When the last item
159
+ # is reached ab empty tuple is passed as the second parameter. This is the
160
+ # classic functional programming `head|tail` list processing idiom.
161
+ # An Enumerator is returned if no block is given.
162
+ #
163
+ # @yieldparam [Object] head the current item for this iteration
164
+ # @yieldparam [Tuple] tail the remaining items (tail) or an empty tuple when
165
+ # processing the last item
166
+ # @return [Enumerable] when no block is given
167
+ def sequence
168
+ return enum_for(:sequence) unless block_given?
169
+ @data.length.times do |index|
170
+ last = @data.length - 1
171
+ if index == last
172
+ yield(@data[index], Tuple.new)
173
+ else
174
+ yield(@data[index], Tuple.new(@data.slice(index+1..last)))
175
+ end
176
+ end
177
+ end
178
+
179
+ # Compares this object and other for equality. A tuple is `eql?` to
180
+ # other when other is a tuple or an array-like object (any object that
181
+ # responds to `to_a`) and the two objects have identical values in the
182
+ # same foxed order.
183
+ #
184
+ # @param [Object] other the other tuple to compare for equality
185
+ # @return [Boolean] true when equal else false
186
+ def eql?(other)
187
+ @data == other.to_a
188
+ end
189
+ alias_method :==, :eql?
190
+
191
+ # Returns true if self contains no items.
192
+ #
193
+ # @return [Boolean] true when empty else false
194
+ def empty?
195
+ @data.empty?
196
+ end
197
+
198
+ # Returns the first element of the tuple or nil when empty.
199
+ #
200
+ # @return [Object] the first element or nil
201
+ def first
202
+ @data.first
203
+ end
204
+ alias_method :head, :first
205
+
206
+ # Returns a tuple containing all the items in self after the first
207
+ # item. Returns an empty tuple when empty or there is only one item.
208
+ #
209
+ # @return [Functional::Tuple] the tail of the tuple
210
+ def rest
211
+ if @data.length <= 1
212
+ Tuple.new
213
+ else
214
+ Tuple.new(@data.slice(1..@data.length-1))
215
+ end
216
+ end
217
+ alias_method :tail, :rest
218
+
219
+ # Create a standard Ruby mutable array containing the tuple items
220
+ # in the same order.
221
+ #
222
+ # @return [Array] the new array created from the tuple
223
+ def to_a
224
+ @data.dup
225
+ end
226
+ alias_method :to_ary, :to_a
227
+
228
+ # Describe the contents of this object in a string.
229
+ #
230
+ # @return [String] the string representation of this object
231
+ #
232
+ # @!visibility private
233
+ def inspect
234
+ "#<#{self.class}: #{@data.to_s}>"
235
+ end
236
+
237
+ # Describe the contents of this object in a string that exactly
238
+ # matches the string that would be created from an identical array.
239
+ #
240
+ # @return [String] the string representation of this object
241
+ #
242
+ # @!visibility private
243
+ def to_s
244
+ @data.to_s
245
+ end
246
+ end
247
+ end