I love #fetch and I feel it makes my code safer. I use it all the time.
{ success: true }[:sucess] is nil (notice that you got a falsy value due to a typo in `success`), while { success: true }.fetch(:sucess) will raise and warn you about your mistake, and Ruby is nice enough to throw a `Did you mean? :success` for you there.
One small tip about fetch: remember that every method in Ruby always fully evaluates all its arguments.
So, if you use it like { data: [ ] }.fetch(:data, get_data_via_very_expensive_method_call), the method `get_data_via_very_expensive_method_call` will always be called, because it's an argument and it will be evaluated BEFORE it's passed to fetch.
That's why one should prefer using the block form in cases like this:
{ data: [ ] }.fetch(:data) { get_data_via_very_expensive_method_call } will only call `get_data_via_very_expensive_method_call` if the Hash doesn't have the :data key.
I imagine the premise of this post is basically to just kick off an exploration into TruffleRuby, which is cool! And an interesting read re: the benchmarking and performance of the implementation.
A shame that refinements don't really get much love, because the third option (in between monkey patching and extending the core library), is to use one of those.
module DigWithFallback
refine Hash do
def dig_fetch(*keys)
# ... impl
end
end
end
class SomeService
using DigWithFallback
def call(params)
params.dig_fetch(:some, :key) { IdentityObject.new }
end
end
> A shame that refinements don't really get much love
Their performance is quite awful on MRI. Any method that was refined is tagged as such, which incur a 40% performance hit (for empty methods)[0]. That alone tend to disqualify them for many use cases.
Then, it might get fixed, but whenever you call `using` all the heap is scanned and all methods caches flushed [1], which is a massive perf hit if it happens often.
TruffleRuby however runs them fast, but since most code out there target MRI, they're not really a good idea except for fringe use cases.
Interesting, I did not know that just calling the original method, even when the refinement method is not used occurs such a cost on CRuby. Seems worth reporting if not already done. No such thing on TruffleRuby though, the only peak performance cost would be for megamorphic calls (rare, even more so in combination with refinements).
I think refinements are better than monkey patching, I mean they won’t cause unexpected behaviour elsewhere, but on the other hand they add a whole new layer of complexity to understanding a code base.
Funny, my first thought on reading this was, "why doesn't she just use `&.` and `||` and call it a day" until I realized that it's a syntax error if you try to use that with `.[]`
Note there is also `data[:response][:message] rescue IdentityObject.new` if you don't mind the clumsy exception handler...
Perhaps this is my Perl background showing, but `.fetch` just doesn't feel Ruby-like. Ruby's syntactical terseness around hashes (and regular expressions, with `=~`, `$1`, and friends) is one of my favorite features of the language. If I have to do my hash grabs with a typed word, I might as well be writing Python
While chained fetch looks a little clumsy, personally I prefer the composition, rather than adding another method and option to the language, which must be learned in order to be used.
To me this is where rails projects can go wrong, when code starts to get too tightly dependent on all kinds of weird options and configured behavior in rails. It makes it difficult to read, even if you're fully versed in all of rails' nooks and crannies, and difficult to modify.
{ success: true }[:sucess] is nil (notice that you got a falsy value due to a typo in `success`), while { success: true }.fetch(:sucess) will raise and warn you about your mistake, and Ruby is nice enough to throw a `Did you mean? :success` for you there.
One small tip about fetch: remember that every method in Ruby always fully evaluates all its arguments.
So, if you use it like { data: [ ] }.fetch(:data, get_data_via_very_expensive_method_call), the method `get_data_via_very_expensive_method_call` will always be called, because it's an argument and it will be evaluated BEFORE it's passed to fetch.
That's why one should prefer using the block form in cases like this:
{ data: [ ] }.fetch(:data) { get_data_via_very_expensive_method_call } will only call `get_data_via_very_expensive_method_call` if the Hash doesn't have the :data key.