Feature #5008

Equal rights for Hash (like Array, String, Integer, Float)

Added by Suraj Kurapati almost 3 years ago. Updated about 1 year ago.

[ruby-core:37932]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto
Category:-
Target version:-

Description

=begin
Hello,

I am using ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux].

Although Ruby has a rich set of primitive data types and structures,
the Hash seems neglected in the Ruby API in comparison to its peers:

  • String: Object#to_s by API
  • Integer: Kernel#Integer by API and Object#to_i by convention
  • Float: Kernel#Float by API and Object#to_f by convention
  • Array: Kernel#Array by API and Object#to_a by convention
  • Hash: Kernel#Hash (issue #3131) and Object#to_hash by convention

In particular, the Hash seems neglected by the Ruby API because:

  • Its convention method (#tohash) is longer than one character (#toh).
  • It did not have a Kernel-level method until recently (see issue #3131).
  • It has no methods for conversion from NilClass, unlike #to_s, a, i, f.

Please rectify this un-orthogonality and grant Hash equal rights by:

  • Establish #to_h as the convention method for converting objects into Hash.
  • Add Kernel#Hash method for converting objects into Hash strictly (see issue #3131).
  • Define NilClass#to_h so that we can convert nil into an empty Hash.

Thanks for your consideration.
=end


Related issues

Related to ruby-trunk - Feature #4151: Enumerable#categorize Assigned
Related to ruby-trunk - Feature #7241: Enumerable#to_h proposal Rejected 10/30/2012
Related to ruby-trunk - Feature #6276: to_h as explicit conversion to Hash Closed 04/10/2012
Duplicates ruby-trunk - Feature #4862: Struct#to_hash Rejected 06/10/2011
Duplicates ruby-trunk - Feature #1400: Please add a method to enumerate fields in OpenStruct Closed 04/23/2009
Precedes ruby-trunk - Feature #3131: add Kernel#Hash() method like Kernel#Array() Closed 07/11/2011 07/11/2011

Associated revisions

Revision 35340
Added by Marc-Andre Lafortune about 2 years ago

  • object.c: Add NilClass#to_h [Feature #6276] [ref #5008] [rubyspec:dc5ecddbd608]

History

#1 Updated by Yui NARUSE almost 3 years ago

  • Status changed from Open to Assigned
  • Assignee set to Yukihiro Matsumoto

First of all, Ruby has two way of the type conversion; implicit and explicit.
toi, tof, tos, toa and so on are explicit conversion.
toint, tostr, to_ary and so on are implicit conversion.

Establish #to_h as the convention method for converting objects into Hash.

If to_h is introduced, it should be a implicit conversion.
But what it is?

Add Kernel#Hash method for converting objects into Hash strictly (see issue #3131).

Why don't you discuss in #3131?

Define NilClass#to_h so that we can convert nil into an empty Hash.

You should show the use case: what is the benefit of the function.

#2 Updated by Suraj Kurapati over 2 years ago

Yui NARUSE wrote:

First of all, Ruby has two way of the type conversion; implicit
and explicit. toi, tof, tos, toa and so on are explicit
conversion. toint, tostr, to_ary and so on are implicit
conversion.

I see, then for Hash:

  • to_h should be explicit conversion
  • to_hash should be implicit conversion.

Add Kernel#Hash method for converting objects into Hash strictly
(see issue #3131).

Why don't you discuss in #3131?

You're right. Sorry for bringing that up.

Define NilClass#to_h so that we can convert nil into an empty
Hash.

You should show the use case: what is the benefit of the function.

The benefit of NilClass#toh is convenience. It is the same reason
why NilClass#to
a and NilClass#to_s exist.

For example, we might process an array that (1) can be empty or (2)
have a hash as the first element:

somearray.first.toh.each_pair do |key, value|
# do some processing ...
end

Without NilClass#to_h, we need to do some extra work:

if hash = somearray.first
hash.each
pair do |key, value|
# do some processing ...
end
end

Furthermore, this extra work seems unfair to Hash because all of the
other primitive data structures (strings, arrays) have NilClass#to_*
conversion methods. :-(

Thanks for your consideration.

#3 Updated by Steve Klabnik over 2 years ago

NilClass#to_h would be useful. Especially for Enumerable types, it's common convention to return an empty container of that type, so that you can chain things together.

The proliferation of nil is often considered a code smell, there's tons of cases where converting nils into values that still mean 'nothing' but have some amount of meaning is useful. See Avdi's talk at RailsConf about Confident Code, for example: http://avdi.org/talks/confident-code-railsconf-2011/

#4 Updated by Suraj Kurapati over 2 years ago

Any chance of this getting into Ruby 1.9.3? Thanks.

#5 Updated by Thomas Sawyer over 2 years ago

+1 I use #to_h often and always have to implement myself when needed. It's annoying.

#6 Updated by Suraj Kurapati over 2 years ago

I have implemented these features in this repo:

https://github.com/sunaku/equal_rights_for_hash

And I have released the code as a nice RubyGem:

http://rubygems.org/gems/equal_rights_for_hash

This will serve as a workaround until these "equal
rights" are granted to Hash in Ruby's core itself.

Cheers.

#7 Updated by Thomas Sawyer over 2 years ago

See facets/to_hash.rb

#8 Updated by Suraj Kurapati over 2 years ago

Very nice! I had to dig around for the source code, so here it is for reference:

https://github.com/rubyworks/facets/blob/master/lib/core/facets/to_hash.rb

My implementation only supports the array-to-hash conversions documented in Hash.[]:

http://rubydoc.info/stdlib/core/1.9.2/Hash.[]

If the Ruby developers want to implement facets/to_hash.rb in Ruby core, then I would be even happier. But for now, the basic equal rights for hash are my goal.

#9 Updated by Suraj Kurapati over 2 years ago

I have simplified my implementation to reflect the MRI implementation of Hash.[]:

https://github.com/sunaku/equal_rights_for_hash/blob/master/lib/equal_rights_for_hash.rb#L11

In the case where the object being converted is an array: If all of its items are arrays, then it is passed directly to Hash.[]. Otherwise, it is passed to Hash.[] in splatted form.

#10 Updated by Suraj Kurapati over 2 years ago

Sorry for the frequent updates. I have added support for Enumerable#to_h now.

https://github.com/sunaku/equal_rights_for_hash/commit/a146f66594caffd6c1b68d864d729a870e516975

The line count is still the same and the implementation remains simple.

#11 Updated by Suraj Kurapati over 2 years ago

Final update (hopefully): I made Kernel#Hash() raise ArgumentError just
like the other Kernel-level methods: Kernel#Integer() and Kernel#Float().

https://github.com/sunaku/equal_rights_for_hash/blob/ab10c5232452f54aa3c88d7520e9169327f92824/lib/equal_rights_for_hash.rb#L5

I have released all these changes as equalrightsfor_hash 0.0.4 gem.

Thanks for your consideration.

#12 Updated by Suraj Kurapati over 2 years ago

=begin

Here is a comparison of core data structure API for your reference:

| | Kernel | Implicit | Explicit | NilClass |
| Class | method | conversion | conversion | conversion |
| --------- | ----------- | ---------- | ---------- | ----------- |
| String | String() | tostr | tos | nil.tos |
| Integer | Integer() | to
int | toi | nil.toi |
| Float | Float() | MISSING | tof | nil.tof |
| Array | Array() | toary | toa | nil.toa |
| Hash | MISSING | to
hash | MISSING | MISSING |

=end

#13 Updated by Suraj Kurapati over 2 years ago

Any update on the status of this request being accepted into Ruby trunk? Thanks.

#14 Updated by Suraj Kurapati about 2 years ago

=begin

Request #3131 was fulfilled, so here is the updated feature matrix:

| | Kernel | Implicit | Explicit | NilClass |
| Class | method | conversion | conversion | conversion |
| --------- | ----------- | ---------- | ---------- | ----------- |
| String | String() | tostr | tos | nil.tos |
| Integer | Integer() | to
int | toi | nil.toi |
| Float | Float() | MISSING | tof | nil.tof |
| Array | Array() | toary | toa | nil.toa |
| Hash | Hash() | to
hash | MISSING | MISSING |

=end

#15 Updated by Marc-Andre Lafortune about 2 years ago

I approve to_h as explicit conversion to a Hash.

It should be created and implemented for NilClass, Hash, Enumerable, Struct, OpenStruct.

It would also pave the way for more generic hash splat if the proposal is accepted (see )

The implementation proposed by Suraj Kurapati can not be accepted in its current form, though. In particular, there should be no Object/Kernel#toh (like there is no Object#toa/toi), and Hash#toh should return a new copy for subclasses (like toa, tos).

I realize that Matz doesn't like Enumerable#toh because there is no perfect "natural" mapping. I feel like any choice here is better than no choice. I think it would be fine if Enumerable#to_h was equivalent to the key-value pair form of Hash[enum.toa], but I'll agree with any other sane definition, as long as it's simple.

For more involved cases, I'm still hoping for Enumerable#associate/categorize.

Thanks.

Marc-André

#16 Updated by Yukihiro Matsumoto about 2 years ago

Hi,

In message "Re: [ruby-trunk - Feature #5008] Equal rights for Hash (like Array, String, Integer, Float)"
on Fri, 16 Mar 2012 02:58:04 +0900, Suraj Kurapati sunaku@gmail.com writes:

|Request #3131 was fulfilled, so here is the updated feature matrix:
|
| | | Kernel | Implicit | Explicit | NilClass |
| | Class | method | conversion | conversion | conversion |
| | --------- | ----------- | ---------- | ---------- | ----------- |
| | String | String() | tostr | tos | nil.tos |
| | Integer | Integer() | to
int | toi | nil.toi |
| | Float | Float() | MISSING | tof | nil.tof |
| | Array | Array() | toary | toa | nil.toa |
| | Hash | Hash() | to
hash | MISSING | MISSING |

This table means nothing. Are you going to expand the table for every
class Ruby would provide, e.g. Complex, Rational, Range, etc?

Repeating myself, unlike other classes in the table, Hash does not
have "natural" conversion from set of values, so that I don't think
it's worth provide to_h method.

In , Marc-Andre claims "any choice here is better
than no choice". But I disagree. For "any choice", we already have
Hash[value].

                        matz.

#17 Updated by Thomas Sawyer about 2 years ago

"Repeating myself, unlike other classes in the table, Hash does not have "natural" conversion from set of values, so that I don't think it's worth provide to_h method."

What does that even mean?

From a practical vantage, I use #to_h all the time and constantly have to define it in my projects. I just did it a few minutes ago, in fact. The need stems from polymorphic behaviour on data mappings.

def some_method(data)
  data = data.to_h
  # ... now I have a known data object to work with ...
end

Anything that responds to #to_h can be passed. Without that we would have to account for every possible type, which is very limiting and basically not practical.

While #tohash could be used, as with the other conversion methods, it conveys the object is a Hash in it's very essence --not simply an object that can fashion a hash from itself regardless of how. An object that responds to #tohash, otoh, would almost certainly respond to #[], #[]= and #each and probably #key?, #keys and #values, too. Although obviously there is no necessary set of public methods it must provide. But is a much stronger indication of the object's nature.

Another use case is simple serialization. #toh is hand-dandy for converting representation of objects to JSON. Again, something like Contact#toh is perfectly useful and sensible, where as Contact#to_hash just doesn't jive.

#18 Updated by Suraj Kurapati about 2 years ago

On Friday, 16 Mar 2012 at 1:13 PM, Yukihiro Matsumoto wrote:

Hi,

In message "Re: [ruby-trunk - Feature #5008] Equal
rights for Hash (like Array, String, Integer, Float)" on Fri, 16 Mar
2012 02:58:04 +0900, Suraj Kurapati sunaku@gmail.com writes:

|Request #3131 was fulfilled, so here is the updated feature matrix:
|
| | | Kernel | Implicit | Explicit | NilClass |
| | Class | method | conversion | conversion | conversion |
| | --------- | ----------- | ---------- | ---------- | ----------- |
| | String | String() | tostr | tos | nil.tos |
| | Integer | Integer() | to
int | toi | nil.toi |
| | Float | Float() | MISSING | tof | nil.tof |
| | Array | Array() | toary | toa | nil.toa |
| | Hash | Hash() | to
hash | MISSING | MISSING |

This table means nothing. Are you going to expand the table for every
class Ruby would provide, e.g. Complex, Rational, Range, etc?

No, this proposal is just for Hash because it is one of the two most
commonly used data structures in Ruby/Perl/JSON/YAML: Array and Hash.

Amdahl's Law says "make the common case fast"; in this case, since Hash
is so frequently used, we would all benefit by making it s/fast/easy/.

That is why I wrote that feature comparison matrix: to highlight the
second-class treatment of Hash (in comparison to Array) in Ruby's API.

Repeating myself, unlike other classes in the table, Hash does not
have "natural" conversion from set of values, so that I don't think
it's worth provide to_h method.

In , Marc-Andre claims "any choice here is better
than no choice". But I disagree. For "any choice", we already have
Hash[value].

Fair enough. I agree with Marc-Andre, but you're the boss. I will just
have to live a gem that provides #to_h. Thanks for your consideration.

#19 Updated by Marc-Andre Lafortune about 2 years ago

Hi,

Yukihiro Matsumoto wrote:

Repeating myself, unlike other classes in the table, Hash does not
have "natural" conversion from set of values, so that I don't think
it's worth provide to_h method.

Thanks for taking the time to reply.

Even if you will not accept Enumerable#to_h, how about to_h for Hash, Struct, OpenStruct and NilClass, which do have a natural conversion to a hash?

Thanks

Marc-André

#20 Updated by Thomas Sawyer about 2 years ago

"Repeating myself, unlike other classes in the table, Hash does not have "natural" conversion from set of values, so that I don't think it's worth provide to_h method."

What does that even mean?

I see, you were referring to Enumerable#to_h and meant there is no "single definitive" method to conversion. True, and to that end, I, with the help of other developers, worked through the general permutations of these:

https://github.com/rubyworks/facets/blob/master/lib/core/facets/to_hash.rb

So leave Enumerable#toh out if you don't like it, but #toh still has a clear definition for a number of other classes, does it not?

#21 Updated by Yukihiro Matsumoto about 2 years ago

Hi,

In message "Re: [ruby-trunk - Feature #5008] Equal rights for Hash (like Array, String, Integer, Float)"
on Sat, 17 Mar 2012 07:13:17 +0900, Marc-Andre Lafortune ruby-core@marc-andre.ca writes:

|Even if you will not accept Enumerable#to_h, how about to_h for Hash, Struct, OpenStruct and NilClass, which do have a natural conversion to a hash?

It seems a better idea than adding Enumerable#toh, except that I am
not sure nil.to
h is a good one.

                        matz.

#22 Updated by Suraj Kurapati about 2 years ago

In my mind, nil.toh should exist for the same reason that
nil.to
a, nil.tos, nil.toi, and nil.to_f exist: convenience.

It allows me to write:

somecomplicatedmethod.to_h.each { ... }

Instead of being forced to write:

if somehash = somecomplicatedmethod
some
hash.to_hash.each { ... }
end

Note that I am only forced to write the above in the specific
case of hash and not for the other abstract data types because,
unlike hash, they all have nil.to_*() conversion methods.

Thanks for your consideration.

#23 Updated by Rodrigo Rosenfeld Rosas about 2 years ago

Or for convenience we could allow nil to respond to each, like in Groovy:

null.each {} // or eachWithIndex - doesn't throw an exception

#24 Updated by Adam Prescott about 2 years ago

On Thu, Mar 29, 2012 at 13:25, rosenfeld (Rodrigo Rosenfeld Rosas) <
rr.rosas@gmail.com> wrote:

Or for convenience we could allow nil to respond to each, like in Groovy:

null.each {} // or eachWithIndex - doesn't throw an exception

Why go down the road of adding this to nil instead of just relying on ||?

foo = some_method || {}
foo.each { ... }

If some_method is controlled, it can be made to return foo || {} if it
really does make sense for it to always return something that client code
can always use as a hash.

#25 Updated by Alex Young about 2 years ago

On 29/03/12 14:28, Adam Prescott wrote:

On Thu, Mar 29, 2012 at 13:25, rosenfeld (Rodrigo Rosenfeld Rosas)
> wrote:

Or for convenience we could allow nil to respond to each, like in
Groovy:

null.each {} // or eachWithIndex - doesn't throw an exception

Why go down the road of adding this to nil instead of just relying on ||?

foo = some_method || {}
foo.each { ... }

If some_method is controlled, it can be made to return foo || {} if it
really does make sense for it to always return something that client
code can always use as a hash.

Would you argue that this is wrong and should be removed?

1.9.3p125 :001 > nil.to_a
=> []

If not, why not? The same argument you've just made about hashes
applies here.

--
Alex

#26 Updated by Rodrigo Rosenfeld Rosas about 2 years ago

Em 29-03-2012 10:28, Adam Prescott escreveu:

On Thu, Mar 29, 2012 at 13:25, rosenfeld (Rodrigo Rosenfeld Rosas)
> wrote:

Or for convenience we could allow nil to respond to each, like in
Groovy:

null.each {} // or eachWithIndex - doesn't throw an exception

Why go down the road of adding this to nil instead of just relying on ||?

foo = some_method || {}
foo.each { ... }

What if some_method returned false instead of nil with this pattern? It
would must probably be some kind of bug hard to track... Unfortunately
Ruby doesn't have a similar operator that will only operate on nil objects.

I'm not saying that we should copy Groovy behavior as I still don't have
a strong opinion on this subject. But Groovy has added "each" and
"collect" to Object and not only to NullObject.
This way, it allows 10.collect{it} == [10] and null.collect{it} == [].
But on the other hand, 'abc'.collect{it} == ['a', 'b', 'c'] and I don't
like this behavior.

While I find that adding each and collect to NilClass might be valid, I
wouldn't like them to be added to Object.

In fact I would prefer to be able to use each and collect with nil
objects rather than adding a "to_h" to it if we're talking about
convenience.

Rodrigo.

#27 Updated by Adam Prescott about 2 years ago

On Thu, Mar 29, 2012 at 14:39, Alex Young alex@blackkettle.org wrote:

Would you argue that this is wrong and should be removed?

1.9.3p125 :001 > nil.to_a
=> []

If not, why not?

Perhaps I wasn't clear (including to myself).

I think nil.toh is probably fine, for the same reason nil.toa is fine,
and it would nice to have it. But, I think adding #each to NilClass is the
wrong way to go to get around being able to call #each on the return value
of some method call. If you really need to_h behaviour, I think you can go
for || before sticking #each on NilClass.

#28 Updated by Yukihiro Matsumoto about 2 years ago

  • Status changed from Assigned to Rejected

#to_hash protocol expects the object to be hash-compatible. Struct is not the case.

Matz.

#29 Updated by Marc-Andre Lafortune about 2 years ago

  • Status changed from Rejected to Open

Hi,

matz (Yukihiro Matsumoto) wrote:

#to_hash protocol expects the object to be hash-compatible. Struct is not the case.

I'm reopening this issue, as the request is not for #to_hash but for #to_h.

I believe you are positive for Hash#to_h, Struct#to_h and OpenStruct#to_h.

You are hesitant about NilClass#to_h, and we were wondering why. I think the translation from nil to {} would be consistent with the transitions to the other basic types, like nil.tos, nil.toi, nil.tof, nil.toc, nil.to_r. It's the only one missing!

#30 Updated by Yukihiro Matsumoto about 2 years ago

  • Status changed from Open to Rejected

Submit new issue for new proposal, e.g. adding hash conversion method to Struct etc.

The reason I hesitate to add toh to nil is because I am not fully satisfied with nil.toa etc.
They are sometimes useful, but often hides bugs and hiders crash-early principle.

Matz.

#31 Updated by Suraj Kurapati about 1 year ago

Hi Matz,

matz (Yukihiro Matsumoto) wrote:

The reason I hesitate to add toh to nil is because I am not fully satisfied with nil.toa etc.
They are sometimes useful, but often hides bugs and hiders crash-early principle.

Thank you very much for accepting nil.toh in issue #6276. ^^

I just read about this feature in Ruby 2.0.0 release notes.

Cheers!

Also available in: Atom PDF