Class: Errgonomic::OptionalHash
- Includes:
- OptionalDig
- Defined in:
- lib/errgonomic/optional_hash.rb
Overview
A companion to Hash whose lookups return Options, composed around a plain Hash rather than subclassing it. Subclassing cannot keep Option semantics: since Ruby 3, most Hash methods return plain Hash instances, so wrapped behavior silently drops off in pipelines. Composition with a small, closed API keeps the semantics honest; reach the plain Hash back with to_h.
Lookups follow key presence, not value truthiness, so a key holding nil is Some(nil). This matches Option presence semantics: the discriminant tells you whether the key was there, and the inner value is yours to judge.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Equal to another OptionalHash wrapping an equal hash; never equal to a plain Hash, mirroring how Some(x) is never equal to x.
-
#[](key) ⇒ Object
Retrieve the value for a key, wrapped in an Option.
-
#[]=(key, value) ⇒ Object
Write through to the underlying hash.
-
#dig(key, *rest) ⇒ Object
Like Hash#dig, but every step checks presence, so the result distinguishes an absent path (None) from a present nil (Some(nil)), which Hash#dig conflates.
- #empty? ⇒ Boolean
- #eql?(other) ⇒ Boolean
- #hash ⇒ Object
-
#initialize(hash = {}) ⇒ OptionalHash
constructor
A new instance of OptionalHash.
- #inspect ⇒ Object
- #key?(key) ⇒ Boolean
- #size ⇒ Object
-
#to_h ⇒ Object
The escape hatch back to a plain Hash: a shallow copy, so hash-shaped code cannot mutate the wrapped state behind the Option semantics.
Constructor Details
#initialize(hash = {}) ⇒ OptionalHash
Returns a new instance of OptionalHash.
19 20 21 22 23 24 25 26 |
# File 'lib/errgonomic/optional_hash.rb', line 19 def initialize(hash = {}) unless hash.is_a?(::Hash) raise Errgonomic::TypeMismatchError, "OptionalHash wraps a Hash, got #{hash.class}" end @hash = hash end |
Instance Method Details
#==(other) ⇒ Object
Equal to another OptionalHash wrapping an equal hash; never equal to a plain Hash, mirroring how Some(x) is never equal to x.
119 120 121 |
# File 'lib/errgonomic/optional_hash.rb', line 119 def ==(other) other.is_a?(OptionalHash) && inner == other.inner end |
#[](key) ⇒ Object
Retrieve the value for a key, wrapped in an Option. A present key with a nil value is Some(nil); only a missing key is None.
36 37 38 39 40 |
# File 'lib/errgonomic/optional_hash.rb', line 36 def [](key) return None() unless @hash.key?(key) Some(@hash[key]) end |
#[]=(key, value) ⇒ Object
Write through to the underlying hash.
48 49 50 |
# File 'lib/errgonomic/optional_hash.rb', line 48 def []=(key, value) @hash[key] = value end |
#dig(key, *rest) ⇒ Object
Like Hash#dig, but every step checks presence, so the result distinguishes an absent path (None) from a present nil (Some(nil)), which Hash#dig conflates. Walks nested Hashes, Arrays, and OptionalHashes; digging into anything else raises, pedantically, where Hash#dig would raise TypeError.
78 79 80 |
# File 'lib/errgonomic/optional_hash.rb', line 78 def dig(key, *rest) optional_dig(@hash, [key, *rest]) end |
#empty? ⇒ Boolean
93 94 95 |
# File 'lib/errgonomic/optional_hash.rb', line 93 def empty? @hash.empty? end |
#eql?(other) ⇒ Boolean
127 128 129 |
# File 'lib/errgonomic/optional_hash.rb', line 127 def eql?(other) other.is_a?(OptionalHash) && inner.eql?(other.inner) end |
#hash ⇒ Object
131 132 133 |
# File 'lib/errgonomic/optional_hash.rb', line 131 def hash [self.class, inner].hash end |
#inspect ⇒ Object
137 138 139 |
# File 'lib/errgonomic/optional_hash.rb', line 137 def inspect "OptionalHash(#{@hash.inspect})" end |
#key?(key) ⇒ Boolean
86 87 88 |
# File 'lib/errgonomic/optional_hash.rb', line 86 def key?(key) @hash.key?(key) end |
#to_h ⇒ Object
The escape hatch back to a plain Hash: a shallow copy, so hash-shaped code cannot mutate the wrapped state behind the Option semantics.
108 109 110 |
# File 'lib/errgonomic/optional_hash.rb', line 108 def to_h @hash.dup end |