ruby picture

RCR 348: Hash#to_h

Submitted by transami (Thu Oct 05 17:55:13 UTC 2006)

Abstract

Add a Hash#to_h method to allow for easier polymorphism with other hash-baring objects.

Problem

When accepting a hash as an argument it's convenient to use #to_h to cast it to a hash, in the same way that one might use #to_s to cast to a string if the parameter might be a symbol. This way any object that responds to #to_h can be used --including an actual Hash. One could use #to_hash instead bu this only works for classes that are strictly hash in character (by convention), but not all classes that might needs this are. For instance:

  class Proc
    # Build a hash out of a Proc.
    #
    #   l = lambda { |s|
    #     s.a = 1
    #     s.b = 2
    #     s.c = 3
    #   }
    #   l.to_h  #=> {:a=>1, :b=>2, :c=>3}
    #
    def to_h
      h = {}
      f = Functor.new{ |op, arg| h[op.chomp('=')] = arg }
      call( f )
      h
    end
  end

Proposal

Simply add Hash#to_h method.

Analysis

Here is an example using the Proc#to_h I gave above.

  def foo(data=nil, &yld)
    data = (data || yld).to_h
    p data[:message]
  end
  foo(:message=>"Hello")

This simply does not work unless there is a Hash#to_h method. And I don't think it's right to define Proc#to_hash.

Moreover adding Hash#to_h adds consistancy, compare with Array#to_a and String#to_s.

Implementation

class Hash
  def to_h
    self
  end
end
ruby picture
Comments Current voting
Trans,

Can you show us more "useful" example? Your usecase does not feel "right" for me, no more than consistency. In general, I buy YAGNI over consistency.

- matz.


Sure, I can give some other use cases, though I think this has been the most useful for me. FYI IAUI (I Am Using It) just as I give in my example. Makes it a snap to accept either a hash, any other object that responds to #to_h or an assignment block as an argument.

While I'm looking, here's a related use for populating a class. This is nice to use in #initialize for the similar reasons (Notice the embedded comment, I think that's mildly relevant).

  require 'facets/core/hash/to_h'
  require 'facets/core/proc/to_h'
  module Kernel
    # Similar to #set_with, but ignores missing setters.
    def populate( data=nil, &yld )
      if data = (data || yld).to_h
        data.each do |k,v|
          send( "#{k}=", v ) rescue nil
        end
      end
      # If the context of the error could be known
      # Maybe this could be used instead of converting
      # the block to a hash.
      #begin
      #  yield self
      #rescue NoMethodError => e
      #  if e.context == self and e.name.to_s =~ /=$/
      #    resume
      #  else
      #    raise e
      #  end
      #end
      self
    end
  end


Trans,

You need to give us more information. I am confusing how adding to_h to Proc can be useful. I don't even understand what your populate method does.

Sorry.

- matz.


That's okay. #populate takes a hash and iterates through each pair calling writer methods by the key's name, ie. send("#{key}=", value), but it won't bomb is there is no writer by the key's name (hence the 'rescue nil'). It also can take a block, which basically is called via yield(self) BUT we don't want it to bomb if the writer method doesn't exist, so we convert it to a hash ans used it that way. The upshot is code like this:

  class Foo
    attr_accessor :x, :y
    def initialize(d=nil,&b)
      populate(d,&b)
    end
    def show
      @x + ' ' + @y
    end
  end
  f1 = Foo.new(:x => "Hello", :y=>"World")
  f1.show #=> "Hello World"
  f2 = Foo.new { |foo|
    foo.x = "Good"
    foo.y = "Bye"
  }
  f2.show #-> "Good Bye"

It's a versitle way to fill out a new object's attributes with either a hash or a block.


Ok, now I understand what populate does. It is a very interesting trick.

But still, your example introduces unnecessary relationship between Proc and Hash. Proc is not automatically convertible to Hash in general. Is there any other object suitable to have to_s method?

-matz.


Just thought of another case: nil. NilClass has #to_s, #to_i and #to_a, but not #to_h. Yet #to_h can be just as useful for similar reasons. Passing nil to a method that normally accepts "hashy" objects and verifies it via #to_h is just as reasonable, and would be expected to take a nil value just as a method would for any of the core types.

  def expect_stringy_thing(x)
    x.to_s
  end
  def expect_integery_thing(x)
    x.to_i
  end
  def expect_arrayee_thing(x)
    x.to_a
  end

All accept nil without issue. Why is Hash exceptional?


Logan Capaldo presents another potential use in ruby-talk:222573.

  class Array
    def to_h
      Hash[*self]
    end
  end
  %w{ a x b y }.to_h  #=> {'a'=>'1', 'b'=>'y'}

Although one could also argue that it should turn an associative array into a hash instead, thus allowing round tripping. Eg.

  a == a.to_h.to_a

I think #to_hash is better in that case, since associative arrays and hashes are essentially similar.

In anycase, the above implementation is another potential usecase for #to_h.

T.


Strongly opposed 0
Opposed 0
Neutral 0
In favor 1
Strongly advocate 1
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .