Зачем возвращать счетчик?

Мне интересно, почему Ruby возвращает Enumerator вместо Array для чего-то похожего на массив Array - очевидный выбор. Например:

'foo'.class
# => String

Большинство людей думают о String как массиве символов.

'foo'.chars.class
# => Enumerator

Итак, почему строки # chars возвращают Enumerable вместо массива? Я предполагаю, что кто-то задумался над этим и решил, что Enumerator более подходит, но я не понимаю, почему.

Ответ 1

Это полностью соответствует духу 1.9: возвращать счетчики, когда это возможно. String # bytes, String # lines, String # codepoints, но также такие методы, как перенастройка Array #, возвращают перечислитель.

В ruby ​​1.8 Строка # to_a привела к массиву строк, но метод ушел в 1.9.

Ответ 2

Если вам нужен массив, вызовите #to_a. Разница между Enumerable и Array заключается в том, что он ленив, а другой нетерпелив. Это хорошая старая память (ленивая) против процессора (нетерпеливая). Видимо, они выбрали ленивый, также потому, что

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"]

Ответ 3

  • Абстракция - факт, что что-то может быть массивом, представляет собой деталь реализации, которую вы не заботитесь о многих случаях использования. Для тех, где вы это делаете, вы всегда можете вызвать .to_a в Enumerable, чтобы получить его.

  • Эффективность. Перечислители являются ленивыми, поскольку Ruby не нужно создавать весь список элементов одновременно, но может делать это по мере необходимости. Поэтому фактически вычисляется только необходимое вам число. Конечно, это приводит к увеличению накладных расходов на каждый предмет, поэтому это компромисс.

  • Расширяемость - причина chars возвращает Enumerable, потому что она сама реализована как перечислитель; если вы передадите ему блок, этот блок будет выполнен один раз за символ. Это означает, что нет необходимости в, например, .chars.each do ... end; вы можете просто сделать .chars do ... end. Это упрощает создание цепей операций над символами строки.

Ответ 4

"Большинство людей думают о String как массив символов"... только если вы думаете, что C или другие языки. ИМХО, ориентация объекта Ruby намного более продвинута, чем это. Большинство операций Array, как правило, больше Enumerable, поэтому, вероятно, это имеет большее значение.

Массив отлично подходит для случайного доступа к различным индексам, но строки редко обращаются к определенному индексу. (и если вы пытаетесь получить доступ к определенному индексу, я подозреваю, что вы, вероятно, занимаетесь школьной работой)

Если вы пытаетесь проверить каждый символ, Enumerable работает. С Enumberable у вас есть доступ к map, each, inject и другие. Также для подстановки существуют строковые функции и регулярные выражения.

Честно говоря, я не могу думать о необходимости реального мира для массива символов.

Ответ 5

Может быть, string в рубине изменен? Тогда наличие Array на самом деле не является очевидным выбором - длина может измениться, например. Но вы все равно захотите перечислить символы...

Кроме того, вы действительно не хотите проходить вокруг фактического хранилища для символов строки, не так ли? Я имею в виду, я не помню много рубинов (это было время), но если бы я разрабатывал интерфейс, я бы раздавал только "копии" для метода/атрибута .chars/безотносительно. Теперь... Вы хотите каждый раз выделять новый массив? Или просто верните маленький объект, который знает, как перечислить символы в строке? Таким образом, сохранение реализации скрыто.

Итак, нет. Большинство людей не воспринимают строку как массив символов. Большинство людей думают о строке как строке. С поведением, определенным библиотекой/языком/временем выполнения. С реализацией вам нужно только знать, когда вы хотите выглядеть отвратительно и все в частном порядке с вещами ниже пояса абстракции.

Ответ 6

Фактически 'foo'.chars передает каждый символ в str в данный блок или возвращает перечислитель, если не задан блок.

Проверьте это:

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"