Reputation: 54984
I''m curious about why ruby returns an Enumerator instead of an Array for something that seems like Array is an obvious choice. For example:
'foo'.class
# => String
Most people think of a String as an array of chars.
'foo'.chars.class
# => Enumerator
So why does String#chars return an Enumerable instead of an Array? I'm assuming somebody put a lot of thought into this and decided that Enumerator is more appropriate but I don't understand why.
Upvotes: 12
Views: 3876
Reputation: 95252
Abstraction - the fact that something may be an Array is an implementation detail you don't care about for many use cases. For those where you do, you can always call .to_a
on the Enumerable to get one.
Efficiency - Enumerators are lazy, in that Ruby doesn't have to build the entire list of elements all at once, but can do so one at a time as needed. So only the number you need is actually computed. Of course, this leads to more overhead per item, so it's a trade-off.
Extensibility - the reason chars
returns an Enumerable is because it is itself implemented as an enumerator; if you pass a block to it, that block will be executed once per character. That means there's no need for e.g. .chars.each do ... end
; you can just do .chars do ... end
. This makes it easy to construct operation chains on the characters of the string.
Upvotes: 5
Reputation: 80065
This completely in accordance with the spirit of 1.9: to return enumerators whenever possible. String#bytes, String#lines, String#codepoints, but also methods like Array#permutation all return an enumerator.
In ruby 1.8 String#to_a resulted in an array of lines, but the method is gone in 1.9.
Upvotes: 2
Reputation: 26979
'Most people think of a String as an array of chars' ... only if you think like C or other languages. IMHO, Ruby's object orientation is much more advanced than that. Most Array
operations tend to be more Enumerable
like, so it probably makes more sense that way.
An array is great for random access to different indexes, but strings are rarely accessed by a particular index. (and if you are trying to to access a particular index, I suspect you are probably doing school work)
If you are trying to inspect each character, Enumerable works. With Enumberable, you have access to map
, each
, inject
, among others. Also for substitution, there are string functions and regular expressions.
Frankly, I can't think of a real world need for an array of chars.
Upvotes: 1
Reputation: 21700
If you want an Array, call #to_a
. The difference between Enumerable
and Array
is that one is lazy and the other eager. It's the good old memory (lazy) vs. cpu (eager) optimization. Apparently they chose lazy, also because
str = "foobar"
chrs = str.chars
chrs.to_a # => ["f", "o", "o", "b", "a", "r"]
str.sub!('r', 'z')
chrs.to_a # => ["f", "o", "o", "b", "a", "z"]
Upvotes: 7
Reputation: 1757
Actually 'foo'.chars passes each character in str to the given block, or returns an enumerator if no block is given.
Check it :
irb(main):017:0> 'foo'.chars
=> #<Enumerable::Enumerator:0xc8ab35 @__args__=[], @__object__="foo", @__method__=:chars>
irb(main):018:0> 'foo'.chars.each {|p| puts p}
f
o
o
=> "foo"
Upvotes: 0
Reputation: 70314
Maybe a string
in ruby is mutable? Then having an Array
isn't really an obvious choice - the length could change, for instance. But you will still want to enumerate the characters...
Also, you don't really want to be passing around the actual storage for the characters of a string, right? I mean, I don't remember much ruby (it's been a while), but if I were designing the interface, I'd only hand out "copies" for the .chars
method/attribute/whatever. Now... Do you want to allocate a new array each time? Or just return a little object that knows how to enumerate the characters in the string? Thus, keeping the implementation hidden.
So, no. Most people don't think of a string as an array of chars. Most people think of a string as a string. With a behavior defined by the library/language/runtime. With an implementation you only need to know when you want to get nasty and all private with stuff below the abstraction belt.
Upvotes: 0