functional-ruby 1.0.0 → 1.1.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,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