Search code examples
rubyreduceinjecthash-of-hashes

how does reduce or inject work in this code


Found this on code wars as one of the solutions. Can someone explain to me how "args.reduce(self)" works in this code; the block after makes sense.

config = { :files => { :mode => 0x777 }, :name => "config" }

class Hash
  def get_value( default, *args )
    args.empty? ? default : args.reduce(self) { |acum, key| acum.fetch(key) } rescue default
  end
end

config.get_value("", :files,:mode)

Solution

  • Suppose we execute

    { :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)
    

    so that within the method

    default #=> 4
    args    #=> [:a, :b, :c]
    self    #=> { :a=>{:b=>{:c=>3 } } }
    

    We then execute the following1:

    args.empty? ? default : args.reduce(self) { |acum, key| acum.fetch(key) } rescue default
      #=> [:a, :b, :c].empty? ? 4 : [:a, :b, :c].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
      #     acum.fetch(key) } rescue 4
      #=> 3
    

    If args #=> [:a, :b], we execute the following:

    [:a, :b].empty? ? 4 : [:a, :b].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
      acum.fetch(key) } rescue 4
      #=> {:c=>3}
    

    If args #=> [:a, :b, :cat], then a KeyError exception is raised and the inline rescue returns the value of default:

    [:a, :b, :cat].empty? ? 4 : [:a, :b, :cat].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
      acum.fetch(key) } rescue 4
      #=> 4
    

    and if args #=> [], [].empty? is true, so the value of default is again returned:

    [].empty? ? 4 : [].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
      acum.fetch(key) } rescue 4
      #=> 4
    

    Fortunately, we no longer have to deal with such nonsense as we were given Hash#dig in Ruby 2.3.0, allowing us to write the following.

    class Hash
      def get_value( default, *keys )
        keys.empty? ? default : dig(*keys) || default
      end
    end
    
    { :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)
      #=> 3
    { :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b)
      #=> {:c=>3} 
    { :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :cat)
      #=> 4 
    { :a=>{:b=>{:c=>3 } } }.get_value(4)
      #=> 4  
    

    Note that the default receiver of dig is self.

    1 Note that instead of ...args.reduce(self) { |acum, key| acum.fetch(key) } rescue default the author of that code could have written ...args.reduce(self) { |acum, key| acum.fetch(key, default) }. See Hash#fetch.