Comments 24
Кстати в Ruby тоже два типа строк, но там немного другие причины их существования.
Это какие такие 2 типа строк в Ruby?
Строка, которая и есть строка
Т.е. первый тип нужен для полного доступа к издевательствам над строковыми данными, а второй, допустим, для указания ключа для хеша (ассоциативного массива, например
"привет, я строка"
— она является самостоятельным объектом. А есть :вот_такая_строка
— это уже некая константа (это не совсем так, но как определение сойдёт), она не создаёт объект в полном понимании этого слова. В следствии этого "строка"
и "строка"
— будут совершенно разными объектами с разными адресами в памяти, но :строка
и :строка
— будут ссылаться на одну и ту же ячейку памяти. Т.е. первый тип нужен для полного доступа к издевательствам над строковыми данными, а второй, допустим, для указания ключа для хеша (ассоциативного массива, например
User[:admin]
)Счего это символ стал строкой? Symbol это сахар вокруг Fixnum, вовсе не контанта это. Кроме того, в свежем руби символы еще и подвержены GC. Вы еще скажите, что замороженые строки это третий вид строк.
Честно, вообще не знаю что за замороженные строки.
Просто меня в своё время удивило наличие двух разных типов, которые выглядят, как последовательность чаров, вот и решил упомянуть. Ну значит я ошибаюсь — в руби только один тип строк, а другой — это Symbol и не является набором чаров. Думаю Вам, как более сведующему в тонкостях этого, безоговорочно красивого языка — всё же виднее.
Просто меня в своё время удивило наличие двух разных типов, которые выглядят, как последовательность чаров, вот и решил упомянуть. Ну значит я ошибаюсь — в руби только один тип строк, а другой — это 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 и получить массив всех символов в системе.
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 и получить массив всех символов в системе.
Насколько подсказывает мой опыт, тип Символ это аналог перечислимого значения, уникального в пределах всей программы. Т.е., он выполняет роль некоторого уникального (но при этом читаемого в коде) идентификатора. Символ ближе к
Главная проблема, как указывают выше, это то, что символы во многих языках с gc не утилизируются (например, если мне не изменяет память, в erlang). Если в рубине утилизируются, то это очень хорошо, это очень здорово.
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, которые мне вспомнились) приводятся короткие примеры, которые ясно демонстрируют проблему. Да, на практике никто прямо так не напишет, но реальные ошибки часто «размазаны» по большому количеству кода — не заталкивать же сотни строчек кода в презентацию.
Обычно (в презентациях и статьях про Rust, которые мне вспомнились) приводятся короткие примеры, которые ясно демонстрируют проблему. Да, на практике никто прямо так не напишет, но реальные ошибки часто «размазаны» по большому количеству кода — не заталкивать же сотни строчек кода в презентацию.
Не воспринимайте эту фразу, как C++ — плохой, а X — хороший.
Скорее, это значит, что C++, за свою долгую жизнь оброс различными здоровыми практиками и идиомами, но даже при всём желании комитета разработчиков он не сможет ввести некоторые фичи, в силу громадного груза обратной совместимости.
С другой стороны, язык X (Rust, например), не имеет такого груза; он может свободно заимствовать лучшие идеи из C++ и других языков, и, возможно, когда-нибудь станет следующим словом в системном программировании.
Не нужно всем сразу бросать C++ и кидаться писать на Rust. C++ это основа, которая всем нужна, на нём написано громадное количество жизненно важного для индустрии кода, но это не значит, что он никогда не будет заменён чем-то новым.
Скорее, это значит, что C++, за свою долгую жизнь оброс различными здоровыми практиками и идиомами, но даже при всём желании комитета разработчиков он не сможет ввести некоторые фичи, в силу громадного груза обратной совместимости.
С другой стороны, язык X (Rust, например), не имеет такого груза; он может свободно заимствовать лучшие идеи из C++ и других языков, и, возможно, когда-нибудь станет следующим словом в системном программировании.
Не нужно всем сразу бросать C++ и кидаться писать на Rust. C++ это основа, которая всем нужна, на нём написано громадное количество жизненно важного для индустрии кода, но это не значит, что он никогда не будет заменён чем-то новым.
Я не фанат Rust, но совсем недавно сделал ошибку в C++, с которой довольно долго разбирался, и которую мог бы предотвратить borrow checker из Rust.
Обсуждение проблемы — gcc.gnu.org/bugzilla/show_bug.cgi?id=60966
Мой код был весьма похож на пример из баг-трекера:
Суть в том, что
Нужно передавать владение promise в лямбда-функцию, но синтаксис С++11 не позволяет этого сделать, так можно только в C++14.
Код ревьюили несколько человек, но ошибку никто не заподозрил.
Обсуждение проблемы — 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 не сможет гарантировать отсутствие ошибки в подобном коде при условии наличия бага в используемой библиотеке…
Но тогда есть способ ошибочно указать компилятору то же самое — а значит, borrow checker не сможет гарантировать отсутствие ошибки в подобном коде при условии наличия бага в используемой библиотеке…
future.wait();
Для кого придуман был?
Напоминаю, что 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()
:-)
Sign up to leave a comment.
Почему ваша первая реализация FizzBuzz на Rust может не работать