Loading…
Diggin’ and Fetchin’ with TruffleRuby
2023-10-18
- Source
- Shopify
- Published
- Added to Yomu
Summary
The investigation starts with production Ruby code that chains Hash#fetch calls and uses an empty-hash default to return an IdentityObject for missing nested data. It compares that workaround with Hash#dig, which is cleaner but does not provide fetch's same control over missing keys, defaults, and explicit nil values. To prototype a combined Hash#dig_fetch method, the author modifies TruffleRuby, using Primitive's hash_get_or_undefined and undefined? to distinguish absent keys from keys containing nil, then replaces recursive traversal with an iterative implementation in shared Diggable logic. For a hash with nine nested keys, iterative changes lift dig from about 2.5M to 16M iterations per second and lift dig_fetch from about 2.5M to 15.5M. The dig_fetch prototype enables the original refactor, but it is not ready for general Ruby adoption because Array and Struct interoperability and corresponding MRI work remain.
Context
Production Ruby code used chained Hash#fetch calls with an empty-hash default to retrieve nested data, but the pattern was difficult to read. Hash#dig offered a cleaner traversal, yet it lacked fetch's flexibility for raising errors, returning defaults, and distinguishing missing keys from keys explicitly set to nil. The investigation also uncovered a performance issue in TruffleRuby's recursive dig implementation.
Approach / What changed
The author prototyped Hash#dig_fetch in TruffleRuby, using Primitive's hash_get_or_undefined and undefined? methods to distinguish missing keys from explicit nil values. The implementation was then refactored from recursive traversal to iterative logic in a shared Diggable package, improving both dig and dig_fetch performance. The resulting changes were shipped to TruffleRuby for dig, while dig_fetch remained a prototype.
Takeaways
- Hash#fetch can return a supplied default, including an empty hash, while Hash#dig returns nil for missing keys and does not provide the same built-in control over errors, defaults, and explicit nil values.
- TruffleRuby's Primitive methods hash_get_or_undefined and undefined? distinguish missing hash keys from keys whose values are explicitly nil.
- For a hash with nine nested keys, iterative traversal improved dig from about 2.5M to 16M iterations per second and dig_fetch from about 2.5M to 15.5M.