Как стать автором
Обновить

Комментарии 24

Кстати в Ruby тоже два типа строк, но там немного другие причины их существования.
Это какие такие 2 типа строк в Ruby?
Строка, которая и есть строка "привет, я строка" — она является самостоятельным объектом. А есть :вот_такая_строка — это уже некая константа (это не совсем так, но как определение сойдёт), она не создаёт объект в полном понимании этого слова. В следствии этого "строка" и "строка" — будут совершенно разными объектами с разными адресами в памяти, но :строка и :строка — будут ссылаться на одну и ту же ячейку памяти.

Т.е. первый тип нужен для полного доступа к издевательствам над строковыми данными, а второй, допустим, для указания ключа для хеша (ассоциативного массива, например User[:admin])
Счего это символ стал строкой? Symbol это сахар вокруг Fixnum, вовсе не контанта это. Кроме того, в свежем руби символы еще и подвержены GC. Вы еще скажите, что замороженые строки это третий вид строк.
Честно, вообще не знаю что за замороженные строки.

Просто меня в своё время удивило наличие двух разных типов, которые выглядят, как последовательность чаров, вот и решил упомянуть. Ну значит я ошибаюсь — в руби только один тип строк, а другой — это Symbol и не является набором чаров. Думаю Вам, как более сведующему в тонкостях этого, безоговорочно красивого языка — всё же виднее.
2.1.2 :002 > a = «a».freeze; b = «a».freeze; c = «a»
2.1.2 :005 > a.object_id == b.object_id
=> true
2.1.2 :006 > a.object_id == c.object_id
=> false

hash1 = { «string» => «value»}
hash2 = { «string» => «value»}
Создаст 6 объектов

hash1 = { :symbol => «value»}
hash2 = { :symbol => «value»}
Создаст 4-5 объекта (символ может быть уже создан)

Символы же являются по-сути строкой только в редакторе. RubyVM с ними уже работает как с числами. Отсюда и профит их использования — они занимают меньше места (но не собираются GC, до недавнего времени), и поиск по hash для них быстрее.

Можно даже вызвать Symbol#all_symbols и получить массив всех символов в системе.
Насколько подсказывает мой опыт, тип Символ это аналог перечислимого значения, уникального в пределах всей программы. Т.е., он выполняет роль некоторого уникального (но при этом читаемого в коде) идентификатора. Символ ближе к enum, чем к строке (просто мы его видим как строку), все символы лежат как бы внутри глобального enum на всю программу. Если сравнить это со статьёй, то автор тоже вводит FizzBuzzItem с перечислимыми значениями, которые суть символы, уникальные в рамках этого типа.

Главная проблема, как указывают выше, это то, что символы во многих языках с gc не утилизируются (например, если мне не изменяет память, в erlang). Если в рубине утилизируются, то это очень хорошо, это очень здорово.
Как я писал выше, в рубине они собираются только в 2.2 и только MRI — bugs.ruby-lang.org/issues/9634
(В действительности, ситуация аналогична той же в C++, за той разницей, что C++ позволит вам сделать кучу глупых ошибок и не даёт каких-либо гарантий работы с памятью. Не спорьте со мной по этому поводу, здесь я лишь цитирую других людей, я не знаю C++ в должной степени.)

Мне нравится, как в каждой статье о Rust, упоминается ужасный и неконтролируемый C++ с кучей проблем. В данной ситуации — статья — чисто о Rust — никаких сравнений с C++ — нет, и если Вы (автор оригинальной статьи) что-то, где-то слышали, что, мол C++ «Ужас и боль», то это не значит, что об этом нужно упоминать беспричинно и безаргументно. Просто ужасно надоело этот шаблон: «Вот — язык Х, а в С++ — будут страшные проблемы, если сделать так же». И в 90% приводится код, который на плюсах никто и никогда не пишет.
Учитывая, что очень многие видят в Rust`е потенциальную безопасную альтернативу C++, то и упоминания C++ в разговорах о Rust мне не кажутся чем-то странным. В данном случае C++ упомянут как пример другого относительного низкоуровневого языка со статической типизацией и схожими сложностями при работе со строками.
>> в 90% приводится код, который на плюсах никто и никогда не пишет.

Обычно (в презентациях и статьях про Rust, которые мне вспомнились) приводятся короткие примеры, которые ясно демонстрируют проблему. Да, на практике никто прямо так не напишет, но реальные ошибки часто «размазаны» по большому количеству кода — не заталкивать же сотни строчек кода в презентацию.
Не воспринимайте эту фразу, как C++ — плохой, а X — хороший.
Скорее, это значит, что C++, за свою долгую жизнь оброс различными здоровыми практиками и идиомами, но даже при всём желании комитета разработчиков он не сможет ввести некоторые фичи, в силу громадного груза обратной совместимости.
С другой стороны, язык X (Rust, например), не имеет такого груза; он может свободно заимствовать лучшие идеи из C++ и других языков, и, возможно, когда-нибудь станет следующим словом в системном программировании.
Не нужно всем сразу бросать C++ и кидаться писать на Rust. C++ это основа, которая всем нужна, на нём написано громадное количество жизненно важного для индустрии кода, но это не значит, что он никогда не будет заменён чем-то новым.
Я не фанат Rust, но совсем недавно сделал ошибку в C++, с которой довольно долго разбирался, и которую мог бы предотвратить borrow checker из Rust.
Обсуждение проблемы — gcc.gnu.org/bugzilla/show_bug.cgi?id=60966
Мой код был весьма похож на пример из баг-трекера:

{
    std::promise<void> promise;
    auto future = promise.get_future();
    io.dispatch([this, &promise] {
        doSomething();
        promise.set_value();
    });
    future.get();
}

Суть в том, что future.get() получал управление до того, как завершался вызов promise.set_value(). В результате set_value() обращался к полям, которые деструктор ~std::promise() уже уничтожил.
Нужно передавать владение promise в лямбда-функцию, но синтаксис С++11 не позволяет этого сделать, так можно только в C++14.

Код ревьюили несколько человек, но ошибку никто не заподозрил.
С другой стороны, вполне возможно написать такую реализацию promise, в которой передача владения именно в этом случае не будет требоваться. А значит, должен быть способ указать компилятору, что некоторый объект можно использовать таким образом.

Но тогда есть способ ошибочно указать компилятору то же самое — а значит, borrow checker не сможет гарантировать отсутствие ошибки в подобном коде при условии наличия бага в используемой библиотеке…
future.wait();

Для кого придуман был?
А с ним что, такой ошибки не будет?
Да, что-то я сморозил — get блокирующий.
Напоминаю, что FuzzBuzz — это такая задача, которую надо решать не только правильно, но еще и быстро :) Так что автору надо было просто остановиться на первом варианте, и не пытаться избавиться от повторных вызовов println. Тем более, что в итоге код стал сложнее, а не проще.
Автор оригинальной статьи, думаю, никаких сложностей с fizzbuzz в любом варианте не испытывает, так как является одним из разработчиков Rust`а. Статья скорее о том, что многие новички, особенно имеющие только опыт работы с высокоуровневыми языками, часто спотыкаются на строках. Для работы с ними, все-таки, уже надо более-менее осознать систему типов и систему владения. Еще людей сильно смущает то, что просто типа «str» в языке нет.
Еще людей сильно смущает то, что просто типа «str» в языке нет.

Вчера нет, а сегодня есть! После введения типов с динамическим размером (DST, dynamically sized types) `str` — полноценный и самостоятельный тип (так же как и срез `[T]`). Другое дело, что из-за его динамического размера с ним самим многого не сделаешь и вся работа ведётся через «толстые» ссылки `&str`, хранящие указатель на `str` и его размер. Не думаю, правда, что это сильно уменьшит смущение людей.
В статье не даётся ответ на поставленный в заголовке вопрос, т.к. первая приведённая программа (внезапно!) работает. Можно записать её короче, что далее и рассматривается, но если программа на известные входные данные выдаёт ожидаемые выходные — она, как правило, считается рабочей.
Это очень знакомый подход для рубистов, но не для питонистов, потому что в Python всё является инструкцией, а не выражением

Пф.

Не повторяйте это в продакшене
for i in range(1, 101):
    res = 'FizzBuzz'  if i % 15 == 0 \ 
          else 'Buzz' if i % 5 == 0 \ 
          else 'Fizz' if i % 3 == 0 \ 
          else i
    print(res)

И это тоже не повторяйте
for res in ('FizzBuzz'  if i % 15 == 0 \
            else 'Buzz' if i % 5 == 0 \
            else 'Fizz' if i % 3 == 0 \
            else i
            for i in range (1, 101)):
    print(res)

А сюда вообще не смотрите
from collections import deque

deque((print('FizzBuzz'  if i % 15 == 0 \
             else 'Buzz' if i % 5 == 0 \
             else 'Fizz' if i % 3 == 0 \
             else i)
       for i in range (1, 101)),
      maxlen=1).pop()

:-)
А зачем в последнем примере дека? Засуньте просто все это в list comprehension, а потом сделайте на него [0] — и кода меньше будет, и понятней.
При N=100 в принципе можно засунуть, но вообще мне жалко памяти :-)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации