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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe9f8b8bb0900055e86688c7995156bb89b5c0af
4
- data.tar.gz: 794627125c2647c8e813b873c69f9be62bf3f206
3
+ metadata.gz: c56d810dbee5d5fb2e4b26daef765a75700d3e4f
4
+ data.tar.gz: e3e27de5c017c07eef5c9d78c590e73251fa98a9
5
5
  SHA512:
6
- metadata.gz: b2e54bc22942b8e2be11ca19fe1c0bd393a1a868bff7f2717d5830efea7e048b53f574763352b778170fe6e46d76d7e42bd566200843595a97142a35572a7941
7
- data.tar.gz: 21c78e069e28438cc55dcdd3f0a2dc0892a5f36236337cd42a28dc8597d04b33f9565a3a46b03ce8c405b3b7b80ade995e2cd4f24c01642335382eaaaf917fc6
6
+ metadata.gz: 84ba63762cb021ceb483e8f175a23c9ea11c12b5fa66764662670b9a688d4d2a0f15bb7baed3b8e2b2207a5b37613b62dc1c90c647bbb68176c0208b41c7cf8e
7
+ data.tar.gz: 7411ea271610c82bbf180e28797ab0e0a0795200d7c77f252e435e098acfe7224236fd59700b72fad726d4a03d43fd1829277f75b14dd8f7f8afd615a44410e0
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ ## Current Release v1.1.0 (August 12, 2014)
2
+
3
+ * A simple implementation of [tuple](http://en.wikipedia.org/wiki/Tuple), an
4
+ immutable, fixed-length list/array/vector-like data structure.
5
+ * `FinalStruct`, a variation on Ruby's `OpenStruct` in which all fields are "final" (meaning
6
+ that new fields can be arbitrarily added but once set each field becomes immutable).
7
+ * `FinalVar`, a thread safe object that holds a single value and is "final" (meaning
8
+ that the value can be set at most once after which it becomes immutable).
9
+
10
+ ### Previous Release v1.0.0 (July 30, 2014)
11
+
12
+ * Protocol specifications inspired by Clojure [protocol](http://clojure.org/protocols),
13
+ Erlang [behavior](http://www.erlang.org/doc/design_principles/des_princ.html#id60128),
14
+ and Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html)
15
+ * Function overloading with Erlang-style [function](http://erlang.org/doc/reference_manual/functions.html)
16
+ [pattern matching](http://erlang.org/doc/reference_manual/patterns.html)
17
+ * Simple, immutable data structures, such as *record* and *union*, inspired by
18
+ [Clojure](http://clojure.org/datatypes), [Erlang](http://www.erlang.org/doc/reference_manual/records.html),
19
+ and [others](http://en.wikipedia.org/wiki/Union_type)
20
+ * `Either` and `Option` classes based on [Functional Java](http://functionaljava.org/) and [Haskell](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html)
21
+ * [Memoization](http://en.wikipedia.org/wiki/Memoization) of class methods based on Clojure [memoize](http://clojuredocs.org/clojure_core/clojure.core/memoize)
22
+ * Lazy execution with a `Delay` class based on Clojure [delay](http://clojuredocs.org/clojure_core/clojure.core/delay)
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Functional Ruby
2
- [![Gem Version](https://badge.fury.io/rb/functional-ruby.png)](http://badge.fury.io/rb/functional-ruby) [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/functional-ruby/badge.png)](https://coveralls.io/r/jdantonio/functional-ruby) [![Code Climate](https://codeclimate.com/github/jdantonio/functional-ruby.png)](https://codeclimate.com/github/jdantonio/functional-ruby) [![Inline docs](http://inch-ci.org/github/jdantonio/functional-ruby.png)](http://inch-ci.org/github/jdantonio/functional-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby)
2
+ [![Gem Version](https://badge.fury.io/rb/functional-ruby.svg)](http://badge.fury.io/rb/functional-ruby) [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/functional-ruby/badge.png)](https://coveralls.io/r/jdantonio/functional-ruby) [![Code Climate](https://codeclimate.com/github/jdantonio/functional-ruby.png)](https://codeclimate.com/github/jdantonio/functional-ruby) [![Inline docs](http://inch-ci.org/github/jdantonio/functional-ruby.png)](http://inch-ci.org/github/jdantonio/functional-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby) [![License](http://img.shields.io/license/MIT.png?color=green)](http://opensource.org/licenses/MIT)
3
3
 
4
4
  **A gem for adding functional programming tools to Ruby. Inspired by [Erlang](http://www.erlang.org/),
5
5
  [Clojure](http://clojure.org/), and [Functional Java](http://functionaljava.org/).**
@@ -8,6 +8,8 @@
8
8
  focus. Version 1.0 is a cohesive set of utilities inspired by other languages
9
9
  but designed to work together in ways idiomatic to Ruby.*
10
10
 
11
+ Please see the [changelog](https://github.com/jdantonio/functional-ruby/blob/master/CHANGELOG.md) for more information.
12
+
11
13
  ## Introduction
12
14
 
13
15
  Two things I love are [Ruby](http://www.ruby-lang.org/en/) and
@@ -38,15 +40,17 @@ Complete API documentation can be found at [Rubydoc.info](http://rubydoc.info/gi
38
40
 
39
41
  * Protocol specifications inspired by Clojure [protocol](http://clojure.org/protocols),
40
42
  Erlang [behavior](http://www.erlang.org/doc/design_principles/des_princ.html#id60128),
41
- and Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html)
43
+ and Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html).
42
44
  * Function overloading with Erlang-style [function](http://erlang.org/doc/reference_manual/functions.html)
43
- [pattern matching](http://erlang.org/doc/reference_manual/patterns.html)
44
- * Simple, immutable data structures, such as *record* and *union*, inspired by
45
+ [pattern matching](http://erlang.org/doc/reference_manual/patterns.html).
46
+ * Simple, thread safe, immutable data structures, such as `Record`, `Union`, and `Tuple`, inspired by
45
47
  [Clojure](http://clojure.org/datatypes), [Erlang](http://www.erlang.org/doc/reference_manual/records.html),
46
- and [others](http://en.wikipedia.org/wiki/Union_type)
47
- * `Either` and `Option` classes based on [Functional Java](http://functionaljava.org/) and [Haskell](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html)
48
- * [Memoization](http://en.wikipedia.org/wiki/Memoization) of class methods based on Clojure [memoize](http://clojuredocs.org/clojure_core/clojure.core/memoize)
49
- * Lazy execution with a `Delay` class based on Clojure [delay](http://clojuredocs.org/clojure_core/clojure.core/delay)
48
+ and other functional languages.
49
+ * Thread safe, immutable `Either` and `Option` classes based on [Functional Java](http://functionaljava.org/) and [Haskell](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html).
50
+ * [Memoization](http://en.wikipedia.org/wiki/Memoization) of class methods based on Clojure [memoize](http://clojuredocs.org/clojure_core/clojure.core/memoize).
51
+ * Lazy execution with a `Delay` class based on Clojure [delay](http://clojuredocs.org/clojure_core/clojure.core/delay).
52
+ * Thread safe data structures, such as `FinalStruct` and `FinalVar`, which can be written to at most once
53
+ before becoming immutable. Based on [Java's `final` keyword](http://en.wikipedia.org/wiki/Final_(Java)).
50
54
 
51
55
  ### Supported Ruby Versions
52
56
 
@@ -75,7 +79,7 @@ require 'functional'
75
79
 
76
80
  ## Examples
77
81
 
78
- Specifying a protocol:
82
+ Specifying a [protocol](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Protocol):
79
83
 
80
84
  ```ruby
81
85
  Functional::SpecifyProtocol(:Name) do
@@ -86,7 +90,27 @@ Functional::SpecifyProtocol(:Name) do
86
90
  end
87
91
  ```
88
92
 
89
- Pattern matching using protocols, type checking, and other options:
93
+ Defining immutable [data structures](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/AbstractStruct) including
94
+ [Either](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Either),
95
+ [Option](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Option),
96
+ [Union](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Union) and
97
+ [Record](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Record)
98
+
99
+ ```ruby
100
+ Name = Functional::Record.new(:first, :middle, :last, :suffix) do
101
+ mandatory :first, :last
102
+ default :first, 'J.'
103
+ default :last, 'Doe'
104
+ end
105
+
106
+ anon = Name.new #=> #<record Name :first=>"J.", :middle=>nil, :last=>"Doe", :suffix=>nil>
107
+ matz = Name.new(first: 'Yukihiro', last: 'Matsumoto') #=> #<record Name :first=>"Yukihiro", :middle=>nil, :last=>"Matsumoto", :suffix=>nil>
108
+ ```
109
+
110
+ [Pattern matching](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/PatternMatching)
111
+ using [protocols](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Protocol),
112
+ [type](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/TypeCheck) checking,
113
+ and other options:
90
114
 
91
115
  ```ruby
92
116
  class Foo
@@ -110,12 +134,8 @@ class Foo
110
134
  "Hello, #{name.first} #{name.last}!"
111
135
  }.when {|name| Satisfy?(:Name) }
112
136
 
113
- defn(:greet, :male, _) { |name|
114
- "Hello, Mr. #{name}!"
115
- }
116
-
117
- defn(:greet, :female, _) { |name|
118
- "Hello, Ms. #{name}!"
137
+ defn(:greet, :doctor, _) { |name|
138
+ "Hello, Dr. #{name}!"
119
139
  }
120
140
 
121
141
  defn(:greet, nil, _) { |name|
@@ -128,7 +148,7 @@ class Foo
128
148
  end
129
149
  ```
130
150
 
131
- Memoization:
151
+ Performance improvement of idempotent functions through [memoization](http://rubydoc.info/github/jdantonio/functional-ruby/master/Functional/Memo):
132
152
 
133
153
  ```ruby
134
154
  class Factors
data/doc/record.txt CHANGED
@@ -124,6 +124,69 @@
124
124
  # is created. Default values should be simple types like `String`, `Fixnum`,
125
125
  # and `Boolean`. If complex operations need performed when setting default
126
126
  # values the a `Class` should be used instead of a `Record`.
127
+ #
128
+ # ### Why Declaration Differs from Ruby's Struct
129
+ #
130
+ # Those familiar with Ruby's `Struct` class will notice one important
131
+ # difference when declaring a `Record`: the block passes to `new` cannot be
132
+ # used to define additional methods. When declaring a new class created from a
133
+ # Ruby `Struct` the block can perform any additional class definition that
134
+ # could be done had the class be defined normally. The excellent
135
+ # [Values](https://github.com/tcrayford/Values) supports this same behavior.
136
+ # `Record` does not allow additional class definitions during declaration for
137
+ # one simple reason: doing so violates two very important tenets of functional
138
+ # programming. Specifically, immutability and the separation of data from
139
+ # operations.
140
+ #
141
+ # `Record` exists for the purpose of creating immutable objects. If additional
142
+ # instance methods were to be defined on a record class it would be possible
143
+ # to violate immutability. Not only could additional, mutable state be added
144
+ # to the class, but the existing immutable attributes could be overridden by
145
+ # mutable methods. The security of providing an immutable object would be
146
+ # completely shattered, thus defeating the original purpose of the record
147
+ # class. Of course it would be possible to allow this feature and trust the
148
+ # programmer to not violate the intended immutability of class, but opening
149
+ # `Record` to the *possibility* of immutability violation is unnecessary and
150
+ # unwise.
151
+ #
152
+ # More important than the potential for immutability violations is the fact
153
+ # the adding additional methods to a record violates the principal of
154
+ # separating data from operations on that data. This is one of the core ideas
155
+ # in functional programming. Data is defined in pure structures that contain
156
+ # no behavior and operations on that data are provided by polymorphic
157
+ # functions. This may seem counterintuitive to object oriented programmers,
158
+ # but that is the nature of functional programming. Adding behavior to a
159
+ # record, even when that behavior does not violate immutability, is still
160
+ # anathema to functional programming, and it is why records in languages like
161
+ # Erlang and Clojure do not have functions defined within them.
162
+ #
163
+ # Should additional methods need defined on a `Record` class, the appropriate
164
+ # practice is to declare the record class then declare another class which
165
+ # extends the record. The record class remains pure data and the subclass
166
+ # contains additional operations on that data.
167
+ #
168
+ # ```ruby
169
+ # NameRecord = Functional::Record.new(:first, :middle, :last, :suffix) do
170
+ # mandatory :first, :last
171
+ # end
172
+ #
173
+ # class Name < NameRecord
174
+ # def full_name
175
+ # "#{first} #{last}"
176
+ # end
177
+ #
178
+ # def formal_name
179
+ # name = [first, middle, last].select{|s| ! s.to_s.empty?}.join(' ')
180
+ # suffix.to_s.empty? ? name : name + ", #{suffix}"
181
+ # end
182
+ # end
183
+ #
184
+ # jerry = Name.new(first: 'Jerry', last: "D'Antonio")
185
+ # ted = Name.new(first: 'Ted', middle: 'Theodore', last: 'Logan', suffix: 'Esq.')
186
+ #
187
+ # jerry.formal_name #=> "Jerry D'Antonio"
188
+ # ted.formal_name #=> "Ted Theodore Logan, Esq."
189
+ # ```
127
190
  #
128
191
  # ## Inspiration
129
192
  #
@@ -6,3 +6,12 @@
6
6
  # mutable references to mutable objects. This cannot be changed. The best
7
7
  # practice it to only encapsulate immutable, frozen, or thread safe objects.
8
8
  # Ultimately, thread safety is the responsibility of the programmer.
9
+ #
10
+ # @!macro [new] thread_safe_final_object
11
+ #
12
+ # @note This is a write-once, read-many, thread safe object that can
13
+ # be used in concurrent systems. Thread safety guarantees *cannot* be made
14
+ # about objects contained *within* this object, however. Ruby variables are
15
+ # mutable references to mutable objects. This cannot be changed. The best
16
+ # practice it to only encapsulate immutable, frozen, or thread safe objects.
17
+ # Ultimately, thread safety is the responsibility of the programmer.
data/lib/functional.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  require 'functional/delay'
2
2
  require 'functional/either'
3
+ require 'functional/final_struct'
4
+ require 'functional/final_var'
3
5
  require 'functional/memo'
4
6
  require 'functional/option'
5
7
  require 'functional/pattern_matching'
6
8
  require 'functional/protocol'
7
9
  require 'functional/protocol_info'
8
10
  require 'functional/record'
11
+ require 'functional/tuple'
9
12
  require 'functional/type_check'
10
13
  require 'functional/union'
14
+ require 'functional/value_struct'
11
15
  require 'functional/version'
12
16
 
13
17
  Functional::SpecifyProtocol(:Disposition) do
@@ -11,6 +11,8 @@ end
11
11
  module Functional
12
12
 
13
13
  # An abstract base class for immutable struct classes.
14
+ #
15
+ # @since 1.0.0
14
16
  module AbstractStruct
15
17
 
16
18
  # @return [Array] the values of all record fields in order, frozen
@@ -47,7 +49,7 @@ module Functional
47
49
  # field values (according to `Object#==`).
48
50
  #
49
51
  # @param [Object] other the other record to compare for equality
50
- # @return [Booleab] true when equal else false
52
+ # @return [Boolean] true when equal else false
51
53
  def eql?(other)
52
54
  self.class == other.class && self.to_h == other.to_h
53
55
  end
@@ -55,7 +57,7 @@ module Functional
55
57
 
56
58
  # @!macro [attach] inspect_method
57
59
  #
58
- # Describe the contents of this record in a string. Will include the name of the
60
+ # Describe the contents of this struct in a string. Will include the name of the
59
61
  # record class, all fields, and all values.
60
62
  #
61
63
  # @return [String] the class and contents of this record
@@ -17,6 +17,8 @@ module Functional
17
17
  #
18
18
  # @see http://clojuredocs.org/clojure_core/clojure.core/delay Clojure delay
19
19
  #
20
+ # @since 1.0.0
21
+ #
20
22
  # @!macro thread_safe_immutable_object
21
23
  class Delay
22
24
 
@@ -77,6 +77,8 @@ module Functional
77
77
  # @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html Haskell Data.Either
78
78
  # @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Obligation.html Concurrent Ruby
79
79
  #
80
+ # @since 1.0.0
81
+ #
80
82
  # @!macro thread_safe_immutable_object
81
83
  class Either
82
84
  include AbstractStruct
@@ -0,0 +1,231 @@
1
+ require_relative 'final_var'
2
+ require 'thread'
3
+
4
+ module Functional
5
+
6
+ # A variation on Ruby's `OpenStruct` in which all fields are "final" (meaning
7
+ # that new fields can be arbitrarily added to a `FinalStruct` object but once
8
+ # set each field becomes immutable). Additionally, predicate methods exist for
9
+ # all fields and these predicates indicate if the field has been set.
10
+ #
11
+ # There are two ways to initialize a `FinalStruct`: with zero arguments or
12
+ # with a `Hash` (or any other object that implements a `to_h` method). The
13
+ # only difference in behavior is that a `FinalStruct` initialized with a
14
+ # hash will pre-define and pre-populate attributes named for the hash keys
15
+ # and with values corresponding to the hash values.
16
+ #
17
+ # @example Instanciation With No Fields
18
+ # bucket = Functional::FinalStruct.new
19
+ #
20
+ # bucket.foo #=> nil
21
+ # bucket.foo? #=> false
22
+ #
23
+ # bucket.foo = 42 #=> 42
24
+ # bucket.foo #=> 42
25
+ # bucket.foo? #=> true
26
+ #
27
+ # bucket.foo = 42 #=> Functional::FinalityError: final accessor 'bar' has already been set
28
+ #
29
+ # @example Instanciation With a Hash
30
+ # name = Functional::FinalStruct.new(first: 'Douglas', last: 'Adams')
31
+ #
32
+ # name.first #=> 'Douglas'
33
+ # name.last #=> 'Adams'
34
+ # name.first? #=> true
35
+ # name.last? #=> true
36
+ #
37
+ # name.middle #=> nil
38
+ # name.middle? #=> false
39
+ # name.middle = 'Noel' #=> 'Noel'
40
+ # name.middle? #=> true
41
+ #
42
+ # name.first = 'Sam' #=> Functional::FinalityError: final accessor 'first' has already been set
43
+ #
44
+ # @since 1.1.0
45
+ #
46
+ # @see Functional::FinalVar
47
+ # @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/ostruct/rdoc/OpenStruct.html
48
+ # @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword
49
+ #
50
+ # @!macro thread_safe_final_object
51
+ class FinalStruct
52
+
53
+ # Creates a new `FinalStruct` object. By default, the resulting `FinalStruct`
54
+ # object will have no attributes. The optional hash, if given, will generate
55
+ # attributes and values (can be a `Hash` or any object with a `to_h` method).
56
+ #
57
+ # @param [Hash] attributes the field/value pairs to set on creation
58
+ def initialize(attributes = {})
59
+ raise ArgumentError.new('attributes must be given as a hash or not at all') unless attributes.respond_to?(:to_h)
60
+ @mutex = Mutex.new
61
+ @attribute_hash = {}
62
+ attributes.to_h.each_pair do |field, value|
63
+ set_attribute(field, value)
64
+ end
65
+ end
66
+
67
+ # @!macro [attach] final_struct_get_method
68
+ #
69
+ # Get the value of the given field.
70
+ #
71
+ # @param [Symbol] field the field to retrieve the value for
72
+ # @return [Object] the value of the field is set else nil
73
+ def get(field)
74
+ @mutex.synchronize {
75
+ get_attribute(field)
76
+ }
77
+ end
78
+ alias_method :[], :get
79
+
80
+ # @!macro [attach] final_struct_set_method
81
+ #
82
+ # Set the value of the give field to the given value.
83
+ #
84
+ # It is a logical error to attempt to set a `final` field more than once, as this
85
+ # violates the concept of finality. Calling the method a second or subsequent time
86
+ # for a given field will result in an exception being raised.
87
+ #
88
+ # @param [Symbol] field the field to set the value for
89
+ # @param [Object] value the value to set the field to
90
+ # @return [Object] the final value of the given field
91
+ #
92
+ # @raise [Functional::FinalityError] if the given field has already been set
93
+ def set(field, value)
94
+ @mutex.synchronize {
95
+ if attribute_has_been_set?(field)
96
+ raise FinalityError.new("final accessor '#{field}' has already been set")
97
+ else
98
+ set_attribute(field, value)
99
+ end
100
+ }
101
+ end
102
+ alias_method :[]=, :set
103
+
104
+ # @!macro [attach] final_struct_set_predicate
105
+ #
106
+ # Check the internal hash to unambiguously verify that the given
107
+ # attribute has been set.
108
+ #
109
+ # @param [Symbol] field the field to get the value for
110
+ # @return [Boolean] true if the field has been set else false
111
+ def set?(field)
112
+ @mutex.synchronize {
113
+ attribute_has_been_set?(field)
114
+ }
115
+ end
116
+
117
+ # Get the current value of the given field if already set else set the value of
118
+ # the given field to the given value.
119
+ #
120
+ # @param [Symbol] field the field to get or set the value for
121
+ # @param [Object] value the value to set the field to when not previously set
122
+ # @return [Object] the final value of the given field
123
+ def get_or_set(field, value)
124
+ @mutex.synchronize {
125
+ attribute_has_been_set?(field) ? get_attribute(field) : set_attribute(field, value)
126
+ }
127
+ end
128
+
129
+ # Get the current value of the given field if already set else return the given
130
+ # default value.
131
+ #
132
+ # @param [Symbol] field the field to get the value for
133
+ # @param [Object] default the value to return if the field has not been set
134
+ # @return [Object] the value of the given field else the given default value
135
+ def fetch(field, default)
136
+ @mutex.synchronize {
137
+ attribute_has_been_set?(field) ? get_attribute(field) : default
138
+ }
139
+ end
140
+
141
+ # Calls the block once for each attribute, passing the key/value pair as parameters.
142
+ # If no block is given, an enumerator is returned instead.
143
+ #
144
+ # @yieldparam [Symbol] field the struct field for the current iteration
145
+ # @yieldparam [Object] value the value of the current field
146
+ #
147
+ # @return [Enumerable] when no block is given
148
+ def each_pair
149
+ return enum_for(:each_pair) unless block_given?
150
+ @mutex.synchronize {
151
+ @attribute_hash.each do |field, value|
152
+ yield(field, value)
153
+ end
154
+ }
155
+ end
156
+
157
+ # Converts the `FinalStruct` to a `Hash` with keys representing each attribute
158
+ # (as symbols) and their corresponding values.
159
+ #
160
+ # @return [Hash] a `Hash` representing this struct
161
+ def to_h
162
+ @mutex.synchronize {
163
+ @attribute_hash.dup
164
+ }
165
+ end
166
+
167
+ # Compares this object and other for equality. A `FinalStruct` is `eql?` to
168
+ # other when other is a `FinalStruct` and the two objects have identical
169
+ # fields and values.
170
+ #
171
+ # @param [Object] other the other record to compare for equality
172
+ # @return [Boolean] true when equal else false
173
+ def eql?(other)
174
+ other.is_a?(self.class) && to_h == other.to_h
175
+ end
176
+ alias_method :==, :eql?
177
+
178
+ # Describe the contents of this object in a string.
179
+ #
180
+ # @return [String] the string representation of this object
181
+ #
182
+ # @!visibility private
183
+ def inspect
184
+ state = to_h.to_s.gsub(/^{/, '').gsub(/}$/, '')
185
+ "#<#{self.class} #{state}>"
186
+ end
187
+ alias_method :to_s, :inspect
188
+
189
+ protected
190
+
191
+ # @!macro final_struct_get_method
192
+ # @!visibility private
193
+ def get_attribute(field)
194
+ @attribute_hash[field.to_sym]
195
+ end
196
+
197
+ # @!macro final_struct_set_method
198
+ # @!visibility private
199
+ def set_attribute(field, value)
200
+ @attribute_hash[field.to_sym] = value
201
+ end
202
+
203
+ # @!macro final_struct_set_predicate
204
+ # @!visibility private
205
+ def attribute_has_been_set?(field)
206
+ @attribute_hash.has_key?(field.to_sym)
207
+ end
208
+
209
+ # Check the method name and args for signatures matching potential
210
+ # final attribute reader, writer, and predicate methods. If the signature
211
+ # matches a reader or predicate, treat the attribute as unset. If the
212
+ # signature matches a writer, attempt to set the new attribute.
213
+ #
214
+ # @param [Symbol] symbol the name of the called function
215
+ # @param [Array] args zero or more arguments
216
+ # @return [Object] the result of the proxied method or the `super` call
217
+ #
218
+ # @!visibility private
219
+ def method_missing(symbol, *args)
220
+ if args.length == 1 && (match = /([^=]+)=$/.match(symbol))
221
+ set(match[1], args.first)
222
+ elsif args.length == 0 && (match = /([^\?]+)\?$/.match(symbol))
223
+ set?(match[1])
224
+ elsif args.length == 0
225
+ get(symbol)
226
+ else
227
+ super
228
+ end
229
+ end
230
+ end
231
+ end