Зачем возвращать счетчик?
Мне интересно, почему 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"