functional-ruby 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +14 -12
- data/doc/memo.md +192 -0
- data/doc/pattern_matching.md +481 -0
- data/doc/protocol.md +219 -0
- data/doc/record.md +247 -0
- data/lib/functional/abstract_struct.rb +8 -8
- data/lib/functional/delay.rb +31 -38
- data/lib/functional/either.rb +48 -45
- data/lib/functional/final_struct.rb +23 -34
- data/lib/functional/final_var.rb +20 -21
- data/lib/functional/memo.rb +33 -24
- data/lib/functional/method_signature.rb +1 -2
- data/lib/functional/option.rb +7 -7
- data/lib/functional/pattern_matching.rb +12 -10
- data/lib/functional/protocol.rb +2 -4
- data/lib/functional/protocol_info.rb +5 -3
- data/lib/functional/record.rb +82 -16
- data/lib/functional/synchronization.rb +88 -0
- data/lib/functional/tuple.rb +14 -4
- data/lib/functional/type_check.rb +0 -2
- data/lib/functional/union.rb +5 -4
- data/lib/functional/value_struct.rb +5 -3
- data/lib/functional/version.rb +1 -1
- data/spec/functional/complex_pattern_matching_spec.rb +1 -2
- data/spec/functional/configuration_spec.rb +0 -2
- data/spec/functional/delay_spec.rb +0 -2
- data/spec/functional/either_spec.rb +0 -1
- data/spec/functional/final_struct_spec.rb +0 -1
- data/spec/functional/final_var_spec.rb +0 -2
- data/spec/functional/memo_spec.rb +7 -10
- data/spec/functional/option_spec.rb +0 -1
- data/spec/functional/pattern_matching_spec.rb +0 -1
- data/spec/functional/protocol_info_spec.rb +0 -2
- data/spec/functional/protocol_spec.rb +1 -3
- data/spec/functional/record_spec.rb +170 -87
- data/spec/functional/tuple_spec.rb +0 -1
- data/spec/functional/type_check_spec.rb +0 -2
- data/spec/functional/union_spec.rb +0 -1
- data/spec/functional/value_struct_spec.rb +0 -1
- metadata +14 -29
- data/doc/memo.txt +0 -192
- data/doc/pattern_matching.txt +0 -485
- data/doc/protocol.txt +0 -221
- data/doc/record.txt +0 -207
- data/doc/thread_safety.txt +0 -17
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require 'functional/protocol'
|
2
|
+
require 'functional/synchronization'
|
2
3
|
|
3
4
|
Functional::SpecifyProtocol(:Struct) do
|
4
5
|
instance_method :fields
|
@@ -11,8 +12,7 @@ end
|
|
11
12
|
module Functional
|
12
13
|
|
13
14
|
# An abstract base class for immutable struct classes.
|
14
|
-
#
|
15
|
-
# @since 1.0.0
|
15
|
+
# @!visibility private
|
16
16
|
module AbstractStruct
|
17
17
|
|
18
18
|
# @return [Array] the values of all record fields in order, frozen
|
@@ -94,7 +94,7 @@ module Functional
|
|
94
94
|
# Set the internal data hash to a copy of the given hash and freeze it.
|
95
95
|
# @param [Hash] data the data hash
|
96
96
|
#
|
97
|
-
# @!visibility private
|
97
|
+
# @!visibility private
|
98
98
|
def set_data_hash(data)
|
99
99
|
@data = data.dup.freeze
|
100
100
|
end
|
@@ -102,7 +102,7 @@ module Functional
|
|
102
102
|
# Set the internal values array to a copy of the given array and freeze it.
|
103
103
|
# @param [Array] values the values array
|
104
104
|
#
|
105
|
-
# @!visibility private
|
105
|
+
# @!visibility private
|
106
106
|
def set_values_array(values)
|
107
107
|
@values = values.dup.freeze
|
108
108
|
end
|
@@ -117,9 +117,9 @@ module Functional
|
|
117
117
|
# @return [Functional::AbstractStruct, Array] the new class and the
|
118
118
|
# (possibly) updated fields array
|
119
119
|
#
|
120
|
-
# @!visibility private
|
120
|
+
# @!visibility private
|
121
121
|
def self.define_class(parent, datatype, fields)
|
122
|
-
struct = Class.new{ include AbstractStruct }
|
122
|
+
struct = Class.new(Functional::Synchronization::Object){ include AbstractStruct }
|
123
123
|
if fields.first.is_a? String
|
124
124
|
parent.const_set(fields.first, struct)
|
125
125
|
fields = fields[1, fields.length-1]
|
@@ -157,7 +157,7 @@ module Functional
|
|
157
157
|
attr_writer :datatype
|
158
158
|
|
159
159
|
fields = [].freeze
|
160
|
-
datatype = :struct
|
160
|
+
datatype = :struct
|
161
161
|
end
|
162
162
|
end
|
163
163
|
end
|
data/lib/functional/delay.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
|
-
require '
|
1
|
+
require 'functional/synchronization'
|
2
2
|
|
3
3
|
module Functional
|
4
4
|
|
5
|
-
# Lazy evaluation of a block yielding an immutable result. Useful for
|
6
|
-
# operations that may never be needed.
|
7
|
-
#
|
5
|
+
# Lazy evaluation of a block yielding an immutable result. Useful for
|
6
|
+
# expensive operations that may never be needed.
|
7
|
+
#
|
8
8
|
# When a `Delay` is created its state is set to `pending`. The value and
|
9
9
|
# reason are both `nil`. The first time the `#value` method is called the
|
10
10
|
# enclosed opration will be run and the calling thread will block. Other
|
11
11
|
# threads attempting to call `#value` will block as well. Once the operation
|
12
12
|
# is complete the *value* will be set to the result of the operation or the
|
13
13
|
# *reason* will be set to the raised exception, as appropriate. All threads
|
14
|
-
# blocked on `#value` will return. Subsequent calls to `#value` will
|
15
|
-
# return the cached value. The operation will only be run once.
|
16
|
-
# any side effects created by the operation will only happen
|
17
|
-
#
|
18
|
-
# @see http://clojuredocs.org/clojure_core/clojure.core/delay Clojure delay
|
19
|
-
#
|
20
|
-
# @since 1.0.0
|
14
|
+
# blocked on `#value` will return. Subsequent calls to `#value` will
|
15
|
+
# immediately return the cached value. The operation will only be run once.
|
16
|
+
# This means that any side effects created by the operation will only happen
|
17
|
+
# once as well.
|
21
18
|
#
|
22
19
|
# @!macro thread_safe_immutable_object
|
23
|
-
|
20
|
+
#
|
21
|
+
# @see http://clojuredocs.org/clojure_core/clojure.core/delay Clojure delay
|
22
|
+
class Delay < Synchronization::Object
|
24
23
|
|
25
24
|
# Create a new `Delay` in the `:pending` state.
|
26
25
|
#
|
@@ -29,79 +28,72 @@ module Functional
|
|
29
28
|
# @raise [ArgumentError] if no block is given
|
30
29
|
def initialize(&block)
|
31
30
|
raise ArgumentError.new('no block given') unless block_given?
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
super
|
32
|
+
synchronize do
|
33
|
+
@state = :pending
|
34
|
+
@task = block
|
35
|
+
end
|
35
36
|
end
|
36
37
|
|
37
38
|
# Current state of block processing.
|
38
39
|
#
|
39
40
|
# @return [Symbol] the current state of block processing
|
40
41
|
def state
|
41
|
-
@
|
42
|
-
@state
|
43
|
-
ensure
|
44
|
-
@mutex.unlock
|
42
|
+
synchronize{ @state }
|
45
43
|
end
|
46
44
|
|
47
45
|
# The exception raised when processing the block. Returns `nil` if the
|
48
46
|
# operation is still `:pending` or has been `:fulfilled`.
|
49
47
|
#
|
50
|
-
# @return [StandardError] the exception raised when processing the block
|
48
|
+
# @return [StandardError] the exception raised when processing the block
|
49
|
+
# else nil.
|
51
50
|
def reason
|
52
|
-
@
|
53
|
-
@reason
|
54
|
-
ensure
|
55
|
-
@mutex.unlock
|
51
|
+
synchronize{ @reason }
|
56
52
|
end
|
57
53
|
|
58
54
|
# Return the (possibly memoized) value of the delayed operation.
|
59
|
-
#
|
55
|
+
#
|
60
56
|
# If the state is `:pending` then the calling thread will block while the
|
61
57
|
# operation is performed. All other threads simultaneously calling `#value`
|
62
58
|
# will block as well. Once the operation is complete (either `:fulfilled` or
|
63
59
|
# `:rejected`) all waiting threads will unblock and the new value will be
|
64
60
|
# returned.
|
65
61
|
#
|
66
|
-
# If the state is not `:pending` when `#value` is called the (possibly
|
67
|
-
# value will be returned without blocking and without performing
|
68
|
-
# again.
|
62
|
+
# If the state is not `:pending` when `#value` is called the (possibly
|
63
|
+
# memoized) value will be returned without blocking and without performing
|
64
|
+
# the operation again.
|
69
65
|
#
|
70
66
|
# @return [Object] the (possibly memoized) result of the block operation
|
71
67
|
def value
|
72
|
-
|
73
|
-
execute_task_once
|
74
|
-
@value
|
75
|
-
ensure
|
76
|
-
@mutex.unlock
|
68
|
+
synchronize{ execute_task_once }
|
77
69
|
end
|
78
70
|
|
79
71
|
# Has the delay been fulfilled?
|
80
72
|
# @return [Boolean]
|
81
73
|
def fulfilled?
|
82
|
-
state == :fulfilled
|
74
|
+
synchronize{ @state == :fulfilled }
|
83
75
|
end
|
84
76
|
alias_method :value?, :fulfilled?
|
85
77
|
|
86
78
|
# Has the delay been rejected?
|
87
79
|
# @return [Boolean]
|
88
80
|
def rejected?
|
89
|
-
state == :rejected
|
81
|
+
synchronize{ @state == :rejected }
|
90
82
|
end
|
91
83
|
alias_method :reason?, :rejected?
|
92
84
|
|
93
85
|
# Is delay completion still pending?
|
94
86
|
# @return [Boolean]
|
95
87
|
def pending?
|
96
|
-
state == :pending
|
88
|
+
synchronize{ @state == :pending }
|
97
89
|
end
|
98
90
|
|
99
91
|
protected
|
100
92
|
|
101
93
|
# @!visibility private
|
102
94
|
#
|
103
|
-
# Execute the enclosed task then cache and return the result if
|
104
|
-
#
|
95
|
+
# Execute the enclosed task then cache and return the result if the current
|
96
|
+
# state is pending. Otherwise, return the cached result.
|
105
97
|
#
|
106
98
|
# @return [Object] the result of the block operation
|
107
99
|
def execute_task_once
|
@@ -114,6 +106,7 @@ module Functional
|
|
114
106
|
@state = :rejected
|
115
107
|
end
|
116
108
|
end
|
109
|
+
@value
|
117
110
|
end
|
118
111
|
end
|
119
112
|
end
|
data/lib/functional/either.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'functional/abstract_struct'
|
2
|
+
require 'functional/protocol'
|
3
|
+
require 'functional/synchronization'
|
3
4
|
|
4
5
|
Functional::SpecifyProtocol(:Either) do
|
5
6
|
instance_method :left, 0
|
@@ -10,45 +11,47 @@ end
|
|
10
11
|
|
11
12
|
module Functional
|
12
13
|
|
13
|
-
# The `Either` type represents a value of one of two possible types (a
|
14
|
-
# It is an immutable structure that contains one and only one
|
15
|
-
# be stored in one of two virtual position, `left` or
|
16
|
-
# context for the encapsulated data.
|
14
|
+
# The `Either` type represents a value of one of two possible types (a
|
15
|
+
# disjoint union). It is an immutable structure that contains one and only one
|
16
|
+
# value. That value can be stored in one of two virtual position, `left` or
|
17
|
+
# `right`. The position provides context for the encapsulated data.
|
17
18
|
#
|
18
|
-
# One of the main uses of `Either` is as a return value that can indicate
|
19
|
-
# success or failure. Object oriented programs generally report errors
|
20
|
-
# either state or exception handling, neither of which work well in
|
21
|
-
# programming. In the former case, a method is called on an object
|
22
|
-
# error occurs the state of the object is updated to reflect the
|
23
|
-
# not translate well to functional programming because they
|
24
|
-
# mutable objects. In the latter, an exception handling block
|
25
|
-
# logic when an exception is thrown. This does not
|
26
|
-
# programming because it eschews side effects
|
27
|
-
#
|
28
|
-
# a powerful and easy-to-use
|
19
|
+
# One of the main uses of `Either` is as a return value that can indicate
|
20
|
+
# either success or failure. Object oriented programs generally report errors
|
21
|
+
# through either state or exception handling, neither of which work well in
|
22
|
+
# functional programming. In the former case, a method is called on an object
|
23
|
+
# and when an error occurs the state of the object is updated to reflect the
|
24
|
+
# error. This does not translate well to functional programming because they
|
25
|
+
# eschew state and mutable objects. In the latter, an exception handling block
|
26
|
+
# provides branching logic when an exception is thrown. This does not
|
27
|
+
# translate well to functional programming because it eschews side effects
|
28
|
+
# like structured exception handling (and structured exception handling tends
|
29
|
+
# to be very expensive). `Either` provides a powerful and easy-to-use
|
30
|
+
# alternative.
|
29
31
|
#
|
30
|
-
# A function that may generate an error can choose to return an immutable
|
31
|
-
# object in which the position of the value (left or right) indicates
|
32
|
-
# of the data. By convention, a `left` value indicates an error and
|
33
|
-
# indicates success. This leaves the caller with no ambiguity
|
34
|
-
# failure, requires no persistent state, and does not
|
35
|
-
# handling facilities.
|
32
|
+
# A function that may generate an error can choose to return an immutable
|
33
|
+
# `Either` object in which the position of the value (left or right) indicates
|
34
|
+
# the nature of the data. By convention, a `left` value indicates an error and
|
35
|
+
# a `right` value indicates success. This leaves the caller with no ambiguity
|
36
|
+
# regarding success or failure, requires no persistent state, and does not
|
37
|
+
# require expensive exception handling facilities.
|
36
38
|
#
|
37
|
-
# `Either` provides several aliases and convenience functions to facilitate
|
38
|
-
# failure/success conventions. The `left` and `right` functions,
|
39
|
-
# derivatives, are mirrored by `reason` and `value`. Failure
|
40
|
-
# presence of a `reason` and success is indicated by the
|
41
|
-
# When an operation has failed the either is in a
|
42
|
-
# operation has successed the either is in a
|
43
|
-
# is to use a Ruby `Exception` as the
|
44
|
-
#
|
45
|
-
#
|
39
|
+
# `Either` provides several aliases and convenience functions to facilitate
|
40
|
+
# these failure/success conventions. The `left` and `right` functions,
|
41
|
+
# including their derivatives, are mirrored by `reason` and `value`. Failure
|
42
|
+
# is indicated by the presence of a `reason` and success is indicated by the
|
43
|
+
# presence of a `value`. When an operation has failed the either is in a
|
44
|
+
# `rejected` state, and when an operation has successed the either is in a
|
45
|
+
# `fulfilled` state. A common convention is to use a Ruby `Exception` as the
|
46
|
+
# `reason`. The factory method `error` facilitates this. The semantics and
|
47
|
+
# conventions of `reason`, `value`, and their derivatives follow the
|
48
|
+
# conventions of the Concurrent Ruby gem.
|
46
49
|
#
|
47
|
-
# The `left`/`right` and `reason`/`value` methods are not mutually exclusive.
|
48
|
-
# can be commingled and still result in functionally correct code. This
|
49
|
-
# should be avoided, however. Consistent use of either `left`/`right`
|
50
|
-
# `reason`/`value` against each `Either` instance will result in more
|
51
|
-
# intent-revealing code.
|
50
|
+
# The `left`/`right` and `reason`/`value` methods are not mutually exclusive.
|
51
|
+
# They can be commingled and still result in functionally correct code. This
|
52
|
+
# practice should be avoided, however. Consistent use of either `left`/`right`
|
53
|
+
# or `reason`/`value` against each `Either` instance will result in more
|
54
|
+
# expressive, intent-revealing code.
|
52
55
|
#
|
53
56
|
# @example
|
54
57
|
#
|
@@ -77,16 +80,14 @@ module Functional
|
|
77
80
|
# @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html Haskell Data.Either
|
78
81
|
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Obligation.html Concurrent Ruby
|
79
82
|
#
|
80
|
-
# @since 1.0.0
|
81
|
-
#
|
82
83
|
# @!macro thread_safe_immutable_object
|
83
|
-
class Either
|
84
|
+
class Either < Synchronization::Object
|
84
85
|
include AbstractStruct
|
85
86
|
|
86
87
|
self.datatype = :either
|
87
88
|
self.fields = [:left, :right].freeze
|
88
89
|
|
89
|
-
# @!visibility private
|
90
|
+
# @!visibility private
|
90
91
|
NO_VALUE = Object.new.freeze
|
91
92
|
|
92
93
|
private_class_method :new
|
@@ -119,7 +120,7 @@ module Functional
|
|
119
120
|
# given error class will be used.
|
120
121
|
#
|
121
122
|
# @example
|
122
|
-
#
|
123
|
+
#
|
123
124
|
# either = Functional::Either.error("You're a bad monkey, Mojo Jojo")
|
124
125
|
# either.fulfilled? #=> false
|
125
126
|
# either.rejected? #=> true
|
@@ -137,7 +138,7 @@ module Functional
|
|
137
138
|
end
|
138
139
|
|
139
140
|
# Projects this either as a left.
|
140
|
-
#
|
141
|
+
#
|
141
142
|
# @return [Object] The left value or `nil` when `right`.
|
142
143
|
def left
|
143
144
|
left? ? to_h[:left] : nil
|
@@ -145,7 +146,7 @@ module Functional
|
|
145
146
|
alias_method :reason, :left
|
146
147
|
|
147
148
|
# Projects this either as a right.
|
148
|
-
#
|
149
|
+
#
|
149
150
|
# @return [Object] The right value or `nil` when `left`.
|
150
151
|
def right
|
151
152
|
right? ? to_h[:right] : nil
|
@@ -213,12 +214,14 @@ module Functional
|
|
213
214
|
# @param [Object] value the value of this either
|
214
215
|
# @param [Boolean] is_left is this a left either or right?
|
215
216
|
#
|
216
|
-
# @!visibility private
|
217
|
+
# @!visibility private
|
217
218
|
def initialize(value, is_left)
|
219
|
+
super
|
218
220
|
@is_left = is_left
|
219
221
|
hsh = is_left ? {left: value, right: nil} : {left: nil, right: value}
|
220
222
|
set_data_hash(hsh)
|
221
223
|
set_values_array(hsh.values)
|
224
|
+
ensure_ivar_visibility!
|
222
225
|
end
|
223
226
|
end
|
224
227
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
require '
|
1
|
+
require 'functional/final_var'
|
2
|
+
require 'functional/synchronization'
|
3
3
|
|
4
4
|
module Functional
|
5
5
|
|
@@ -41,14 +41,11 @@ module Functional
|
|
41
41
|
#
|
42
42
|
# name.first = 'Sam' #=> Functional::FinalityError: final accessor 'first' has already been set
|
43
43
|
#
|
44
|
-
# @since 1.1.0
|
45
|
-
#
|
46
|
-
# @see Functional::FinalVar
|
47
44
|
# @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/ostruct/rdoc/OpenStruct.html
|
48
45
|
# @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword
|
49
46
|
#
|
50
47
|
# @!macro thread_safe_final_object
|
51
|
-
class FinalStruct
|
48
|
+
class FinalStruct < Synchronization::Object
|
52
49
|
|
53
50
|
# Creates a new `FinalStruct` object. By default, the resulting `FinalStruct`
|
54
51
|
# object will have no attributes. The optional hash, if given, will generate
|
@@ -57,10 +54,12 @@ module Functional
|
|
57
54
|
# @param [Hash] attributes the field/value pairs to set on creation
|
58
55
|
def initialize(attributes = {})
|
59
56
|
raise ArgumentError.new('attributes must be given as a hash or not at all') unless attributes.respond_to?(:to_h)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
super
|
58
|
+
synchronize do
|
59
|
+
@attribute_hash = {}
|
60
|
+
attributes.to_h.each_pair do |field, value|
|
61
|
+
ns_set_attribute(field, value)
|
62
|
+
end
|
64
63
|
end
|
65
64
|
end
|
66
65
|
|
@@ -71,9 +70,7 @@ module Functional
|
|
71
70
|
# @param [Symbol] field the field to retrieve the value for
|
72
71
|
# @return [Object] the value of the field is set else nil
|
73
72
|
def get(field)
|
74
|
-
|
75
|
-
get_attribute(field)
|
76
|
-
}
|
73
|
+
synchronize { ns_get_attribute(field) }
|
77
74
|
end
|
78
75
|
alias_method :[], :get
|
79
76
|
|
@@ -91,13 +88,13 @@ module Functional
|
|
91
88
|
#
|
92
89
|
# @raise [Functional::FinalityError] if the given field has already been set
|
93
90
|
def set(field, value)
|
94
|
-
|
95
|
-
if
|
91
|
+
synchronize do
|
92
|
+
if ns_attribute_has_been_set?(field)
|
96
93
|
raise FinalityError.new("final accessor '#{field}' has already been set")
|
97
94
|
else
|
98
|
-
|
95
|
+
ns_set_attribute(field, value)
|
99
96
|
end
|
100
|
-
|
97
|
+
end
|
101
98
|
end
|
102
99
|
alias_method :[]=, :set
|
103
100
|
|
@@ -109,9 +106,7 @@ module Functional
|
|
109
106
|
# @param [Symbol] field the field to get the value for
|
110
107
|
# @return [Boolean] true if the field has been set else false
|
111
108
|
def set?(field)
|
112
|
-
|
113
|
-
attribute_has_been_set?(field)
|
114
|
-
}
|
109
|
+
synchronize { ns_attribute_has_been_set?(field) }
|
115
110
|
end
|
116
111
|
|
117
112
|
# Get the current value of the given field if already set else set the value of
|
@@ -121,9 +116,7 @@ module Functional
|
|
121
116
|
# @param [Object] value the value to set the field to when not previously set
|
122
117
|
# @return [Object] the final value of the given field
|
123
118
|
def get_or_set(field, value)
|
124
|
-
|
125
|
-
attribute_has_been_set?(field) ? get_attribute(field) : set_attribute(field, value)
|
126
|
-
}
|
119
|
+
synchronize { ns_attribute_has_been_set?(field) ? ns_get_attribute(field) : ns_set_attribute(field, value) }
|
127
120
|
end
|
128
121
|
|
129
122
|
# Get the current value of the given field if already set else return the given
|
@@ -133,9 +126,7 @@ module Functional
|
|
133
126
|
# @param [Object] default the value to return if the field has not been set
|
134
127
|
# @return [Object] the value of the given field else the given default value
|
135
128
|
def fetch(field, default)
|
136
|
-
|
137
|
-
attribute_has_been_set?(field) ? get_attribute(field) : default
|
138
|
-
}
|
129
|
+
synchronize { ns_attribute_has_been_set?(field) ? ns_get_attribute(field) : default }
|
139
130
|
end
|
140
131
|
|
141
132
|
# Calls the block once for each attribute, passing the key/value pair as parameters.
|
@@ -147,11 +138,11 @@ module Functional
|
|
147
138
|
# @return [Enumerable] when no block is given
|
148
139
|
def each_pair
|
149
140
|
return enum_for(:each_pair) unless block_given?
|
150
|
-
|
141
|
+
synchronize do
|
151
142
|
@attribute_hash.each do |field, value|
|
152
143
|
yield(field, value)
|
153
144
|
end
|
154
|
-
|
145
|
+
end
|
155
146
|
end
|
156
147
|
|
157
148
|
# Converts the `FinalStruct` to a `Hash` with keys representing each attribute
|
@@ -159,9 +150,7 @@ module Functional
|
|
159
150
|
#
|
160
151
|
# @return [Hash] a `Hash` representing this struct
|
161
152
|
def to_h
|
162
|
-
@
|
163
|
-
@attribute_hash.dup
|
164
|
-
}
|
153
|
+
synchronize { @attribute_hash.dup }
|
165
154
|
end
|
166
155
|
|
167
156
|
# Compares this object and other for equality. A `FinalStruct` is `eql?` to
|
@@ -190,19 +179,19 @@ module Functional
|
|
190
179
|
|
191
180
|
# @!macro final_struct_get_method
|
192
181
|
# @!visibility private
|
193
|
-
def
|
182
|
+
def ns_get_attribute(field)
|
194
183
|
@attribute_hash[field.to_sym]
|
195
184
|
end
|
196
185
|
|
197
186
|
# @!macro final_struct_set_method
|
198
187
|
# @!visibility private
|
199
|
-
def
|
188
|
+
def ns_set_attribute(field, value)
|
200
189
|
@attribute_hash[field.to_sym] = value
|
201
190
|
end
|
202
191
|
|
203
192
|
# @!macro final_struct_set_predicate
|
204
193
|
# @!visibility private
|
205
|
-
def
|
194
|
+
def ns_attribute_has_been_set?(field)
|
206
195
|
@attribute_hash.has_key?(field.to_sym)
|
207
196
|
end
|
208
197
|
|