Repository navigation
Optional collections: OptionalHash, OptionalArray, and additive lookups - #38
Merged
Merged
Conversation
Lookups follow key presence like Rust's HashMap#get: a present key holding nil is Some(nil), only a missing key is None. dig checks presence at every step, keeping absent paths distinct from present nils, and raises TypeMismatchError on non-collections where core dig would TypeError. Composition rather than subclassing: since Ruby 3, most Hash methods return plain Hash instances, so a subclass sheds Option semantics in any pipeline (select, transform_values). A small closed API cannot drift; to_h hands back a detached copy. Hash itself gains only two additive methods, fetch_option and into_optional; the ecosystem leans too heavily on Hash semantics to change any existing behavior.
fetch_option follows element presence like Rust's slice get, with bounds checked and Some(nil) for present nil elements. The OptionalArray view adds first/last as Options, mirroring Rust's slice first/last. Shares the presence-checking dig walk with OptionalHash via OptionalDig.
nz
enabled auto-merge
August 7, 2026 18:52
h3h
reviewed
Aug 7, 2026
h3h
reviewed
Aug 7, 2026
Comment on lines
+81
to
+83
| def key?(key) | ||
| @hash.key?(key) | ||
| end |
Member
There was a problem hiding this comment.
Probably a small nit, but I’d use delegate for these. (Is that a stdlib thing yet?)
Member
Author
There was a problem hiding this comment.
No idea! And if it is then we have to adopt some kind of minimum supported ruby version convention here. ¯\_(ツ)_/¯
Converting a nested OptionalHash/OptionalArray back to its plain collection allocated a throwaway shallow dup per wrapper and re-implemented semantics the wrapper already owns. Delegate the remaining keys to the nested wrapper's own dig instead; a doctest pins the nested-wrapper path.
h3h
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #6, keeping its idea (Option-returning hash lookups, with the explicit-key-check semantics from its second commit) and replacing the vehicle. The both-and shape agreed in review:
Additive core extensions, nothing else touched
Hash#fetch_option/Array#fetch_optionreturn an Option following presence, exactly as Rust'sHashMap::getand slicegetdo: present-but-nil isSome(nil), only absent isNone().Hash#into_optional/Array#into_optionalwrap the collection in the view classes (precedent:Hash#with_indifferent_accessis itself an additive core patch). No existing Hash/Array behavior changes; verified the names collide with nothing in ActiveSupport 8.1 (Hashhas onlyto_options/extractable_options?in that namespace).Composition, not subclassing
Errgonomic::OptionalHashandErrgonomic::OptionalArraywrap a plain collection with a deliberately small, closed API:[],[]=,dig,key?/bounds,empty?,size,to_h/to_a(detached copies), equality satisfying theeql?/hashcontract (#30), readableinspect(#21), andfirst/lastas Options on the array (Rust's slicefirst/last).Why not
< Hash, verified against Ruby 3.4 while reviewing #6:select/transform_valueson a Hash subclass return plainHash, so Option semantics silently shed in any pipeline. Doing this properly is the HWIA approach: override the entire ~40-method surface.Hash#digbypasses an overridden[]at the C level, so OptionalHash and a bunch of Rubocop cleanup #6'sdignever saw the wrapped values its unwrap-loop expected.[]anddigdisagreed about stored nil (Some(nil)vsNone()). Here one shared walk (OptionalDig) gives both classes the same semantics, anddigdistinguishes absent path (None) from present nil (Some(nil)) — the one thing coredigcannot express.Digging into a non-collection raises
TypeMismatchErrorin the gem's pedantic style, where coredigraisesTypeError.Specified as doctests (23 new); README gains an Optional collections section.