Сижу вот изучаю Rust
Как вы, наверное, знаете, в C/C++ есть
И компилятор там всеми силами пытается его соптимайзить, фигачит
В Rust есть по сути тоже самое, но еще и со строками (
Короче - сам поиск примерно никак, используется линейный поиск, но оптимизируется сравнение строк.
Единственное, если размер строк разный, то он сделает jump table по их размеру (по сути, как с обычными числами)
А вот со сравнением строк уже интереснее. Да да, это скорее всего уже не сам раст, сколько llvm, но все равно интересно
1) Если строки <8 байт, то строка запишется как число, и компилятор просто прочитает строку как число и сделает сравнение
2) Если строки <16 байт, то сделает два сравнения (два числа 8 байт)
3) Если строки <32(?) байта, то фиганет SIMD сравнение (строки все еще числа)
4) Иначе, вызовет какую-то функцию сравнения длинных строк, которая, думаю, использует какие-то идеи из 1-3
Обидно конечно, что он не оптимайзит для длинных строк ничего. В моем случае (см картинку) очевидно, что можно понять, какая именно строка нужна по первому символу даже, и дальше сделать всего-лишь 1 проверку.
May be one day я решусь залезть в llvm...
Так что если вдруг кто-то решит переписать ejudge на раст, то придется и для него кодогенерировать бор для команд...
Как вы, наверное, знаете, в C/C++ есть
switch
, который работает для чиселИ компилятор там всеми силами пытается его соптимайзить, фигачит
jump table
какой-то или около тогоВ Rust есть по сути тоже самое, но еще и со строками (
match str
), и возник вопрос, а как оно там, собственно, оптимизируетсяКороче - сам поиск примерно никак, используется линейный поиск, но оптимизируется сравнение строк.
Единственное, если размер строк разный, то он сделает jump table по их размеру (по сути, как с обычными числами)
А вот со сравнением строк уже интереснее. Да да, это скорее всего уже не сам раст, сколько llvm, но все равно интересно
1) Если строки <8 байт, то строка запишется как число, и компилятор просто прочитает строку как число и сделает сравнение
2) Если строки <16 байт, то сделает два сравнения (два числа 8 байт)
3) Если строки <32(?) байта, то фиганет SIMD сравнение (строки все еще числа)
4) Иначе, вызовет какую-то функцию сравнения длинных строк, которая, думаю, использует какие-то идеи из 1-3
Обидно конечно, что он не оптимайзит для длинных строк ничего. В моем случае (см картинку) очевидно, что можно понять, какая именно строка нужна по первому символу даже, и дальше сделать всего-лишь 1 проверку.
May be one day я решусь залезть в llvm...
Так что если вдруг кто-то решит переписать ejudge на раст, то придется и для него кодогенерировать бор для команд...
godbolt.org
Compiler Explorer - Rust (rustc 1.55.0)
// Type your code here, or load an example.
pub fn some(s: &str) -> i32 {
match s {
"asomessmesomdsomesomesomesomessmesomdsomesomesome" => 0,
"bothrothrothrothrothrothrothrothrothrothrothrothr" => 1,
"cstufstufstufstufstufstuf…
pub fn some(s: &str) -> i32 {
match s {
"asomessmesomdsomesomesomesomessmesomdsomesomesome" => 0,
"bothrothrothrothrothrothrothrothrothrothrothrothr" => 1,
"cstufstufstufstufstufstuf…
👍1
В Go можно написать простой rate-limiter, который ограничивает нагрузку, двумя способами: перед запуском горутины, или внутри нее. Я вот не задумывался, что они отличаются довольно сильно в плане порядка обработки запросов
За подробностями прошу сюда: https://leviska.notion.site/3dbe7633fd5449ff99c485d147f2d420
За подробностями прошу сюда: https://leviska.notion.site/3dbe7633fd5449ff99c485d147f2d420
Лев's Notion on Notion
Рейтлимитер: внутри или снаружи | Notion
Начнем с простого: зачем нужен рейтлимитер?
👍1
Написал что-то похожее на kwargs, но статически типизированное и на расте. Не спрашивайте меня зачем. playground
Немного макросов, и я думаю, что можно сделать даже красиво
https://twitter.com/leviska0/status/1536502738237931520
Немного макросов, и я думаю, что можно сделать даже красиво
https://twitter.com/leviska0/status/1536502738237931520
👍3
Я все долго хотел написать пост про раст, и, наверное, когда-то его напишу, но недавно нашел это прекрасное видео, которое рассказывает про тот самый safety, что дает Rust, сравнивая куски кода с C++. Если вы все задавались вопросом "да кто такая безопасность в этом вашем расте", то видео очень хорошее
https://www.youtube.com/watch?v=IPmRDS0OSxM
https://www.youtube.com/watch?v=IPmRDS0OSxM
YouTube
A Firehose of Rust, for busy people who know some C++
Slides: https://jacko.io/firehose_of_rust
The slower version of this talk (2h32m): https://youtu.be/FSyfZVuD32Y
Contents:
0:00:00 introduction
0:04:03 references and mutable aliasing
0:06:54 reference lifetime examples
0:22:12 mutable aliasing examples
0:50:16…
The slower version of this talk (2h32m): https://youtu.be/FSyfZVuD32Y
Contents:
0:00:00 introduction
0:04:03 references and mutable aliasing
0:06:54 reference lifetime examples
0:22:12 mutable aliasing examples
0:50:16…
Где-то год назад я полностью осознал казалось бы очень простую мысль: "все программирование, все, с чем мы работаем, это все код. Код, который зачастую опенсорсный, куда ты можешь зайти, почитать его, и если вдруг нужно - поменять"
"Well, duh", скажете вы, но главная суть этой идеи в том, что менять опенсорсный код - это нормально и не так уж и сложно. Например, если вы выбираете какую-то библиотеку или инструмент для работы, и он вот чуть чуть не делает того, что вам надо, то у вас есть возможность просто сесть и доделать это. Не надо ныть, что "уу хорошая библиотека, но вот фичу не умеет", не надо искать другие менее популярные (и более багованые) аналоги, просто сядь и пофикси (лол)
Вчера вот я вышел на новый уровень какой-то: в Go есть хорошая ORM библиотека для БД gorm, и есть хороший инструмент для миграций goose, написанный на го, и в котором можно в том числе писать миграции на го. Но вторая штука не умеет работать из коробки с
Я пошел в репу и нашел PR, который за 10 строчек добавляет возможность удобно подрубить
Три часа дебаггинга спустя, я нашел забытый
Но почему я решил этим поделиться: для меня лично это был новый уровень, когда я фиксил даже не саму либу, а чей то пр этой либы. Т.е. мало того, что я решил не искать аналог и пофиксить уже хороший инструмент, так я не стал в очередной раз городить велосипед, а нашел уже чей-то другой велосипед и починил пару костылей в нем.
Вторая причина, почему я хотел этим поделиться, это попытаться завлечь вас делать так же. Я часто обсуждаю всякую прогу с друзьями и коллегами, и часто слышу как люди сталкиваются с похожими проблемами "вот есть штука, но она делает почти все, что надо", на что я отвечаю "ну просто пофикси лол))", и люди думают, что я шучу, мол "да зачем", "ой лезть еще туда", "я не смогу" и т.д., но при этом ни разу не пробовали так делать. За последние два года у меня уже накопилось несколько контрибьюшенов в опенсорс софт, и все эти контрибьюшены (не считая опенсорсных репозиториев с работы) были ровно такими же: есть хороший инструмент, но нужна какая-то мелочь, сел, разобрался, добавил эту мелочь, получил свои фичи и плюс в карму за помощь в разработке опенсорса. И скажу честно: в первые разы было очень тяжело, я действительно тратил много времени (несколько дней), чтобы просто разобраться в чужом коде, но это просто скилл, причем очень полезный, и я крайне рекомендую в следующий раз, когда от хорошего инструмента будет нужна еще какая-то мелочь, просто сесть и добавить ее
"Well, duh", скажете вы, но главная суть этой идеи в том, что менять опенсорсный код - это нормально и не так уж и сложно. Например, если вы выбираете какую-то библиотеку или инструмент для работы, и он вот чуть чуть не делает того, что вам надо, то у вас есть возможность просто сесть и доделать это. Не надо ныть, что "уу хорошая библиотека, но вот фичу не умеет", не надо искать другие менее популярные (и более багованые) аналоги, просто сядь и пофикси (лол)
Вчера вот я вышел на новый уровень какой-то: в Go есть хорошая ORM библиотека для БД gorm, и есть хороший инструмент для миграций goose, написанный на го, и в котором можно в том числе писать миграции на го. Но вторая штука не умеет работать из коробки с
gorm
, и в итоге по сути надо делать два соединения с бд и работать с gorm
через глобальную переменную, что не является проблемой, но блин некрасиво!Я пошел в репу и нашел PR, который за 10 строчек добавляет возможность удобно подрубить
gorm
. Но чел этот пр видимо не запускал, и когда я запустил его у себя локально, то у меня все ушло в вечный цикл и ничего не работало.Три часа дебаггинга спустя, я нашел забытый
return nil
, форкнул форк, пофиксил в нем багу, и теперь все хорошо работаетНо почему я решил этим поделиться: для меня лично это был новый уровень, когда я фиксил даже не саму либу, а чей то пр этой либы. Т.е. мало того, что я решил не искать аналог и пофиксить уже хороший инструмент, так я не стал в очередной раз городить велосипед, а нашел уже чей-то другой велосипед и починил пару костылей в нем.
Вторая причина, почему я хотел этим поделиться, это попытаться завлечь вас делать так же. Я часто обсуждаю всякую прогу с друзьями и коллегами, и часто слышу как люди сталкиваются с похожими проблемами "вот есть штука, но она делает почти все, что надо", на что я отвечаю "ну просто пофикси лол))", и люди думают, что я шучу, мол "да зачем", "ой лезть еще туда", "я не смогу" и т.д., но при этом ни разу не пробовали так делать. За последние два года у меня уже накопилось несколько контрибьюшенов в опенсорс софт, и все эти контрибьюшены (не считая опенсорсных репозиториев с работы) были ровно такими же: есть хороший инструмент, но нужна какая-то мелочь, сел, разобрался, добавил эту мелочь, получил свои фичи и плюс в карму за помощь в разработке опенсорса. И скажу честно: в первые разы было очень тяжело, я действительно тратил много времени (несколько дней), чтобы просто разобраться в чужом коде, но это просто скилл, причем очень полезный, и я крайне рекомендую в следующий раз, когда от хорошего инструмента будет нужна еще какая-то мелочь, просто сесть и добавить ее
👍7
Как выражать логику через систему типов, или пишем код, который не дает багать
https://leviska.notion.site/33f6445acb704440b57faae727123572
Я долго хотел написать этот пост, но все не мог найти хороший пример и не скатиться в "объяснение что такое Rust и почему он крута" на 30 страниц. И вроде бы получилось.
Если вы программируете на C++ и не понимаете вот этого хайпа вокруг Rust насчет "компилируется - значит работает" - пост для вас, с примерами на C++ и сравнением двух реализаций
https://leviska.notion.site/33f6445acb704440b57faae727123572
Я долго хотел написать этот пост, но все не мог найти хороший пример и не скатиться в "объяснение что такое Rust и почему он крута" на 30 страниц. И вроде бы получилось.
Если вы программируете на C++ и не понимаете вот этого хайпа вокруг Rust насчет "компилируется - значит работает" - пост для вас, с примерами на C++ и сравнением двух реализаций
Лев's Notion on Notion
Как выражать логику через систему типов | Notion
Или пишем код, который не дает багать
👍7
Еще когда я даже не учил Rust, но спрашивал у знающих знакомых "как работает XXX", меня часто не удовлетворял ответ: казалось, что для того, чтобы писать такой же эффективный код на
Возьмем
Плюсы говорят "вот тип, вы можете проверять, лежит ли в нем что-то, а можете не проверять, так как уже проверяли когда-то до этого, нам пофиг. Но если не проверите, то будет бобо"
Раст говорит "вот тип, вы или должны проверить, или должны явно указать, что вы умнее через
И во времена, когда я был ярым C++сером, мне казалось, что это будет как в плюсах, только неудобнее, потому что я даже не подозревал, что можно лучше.
Простой пример, как в C++ проверить, что что-то лежит внутри
Ногоспади спасибо разрабам раста конечно же никто так не пишет. Вместо этого, в Rust вы пытаетесь показать свою идею через систему типов. В данном случае вы хотите показать, что "вот тут я проверил
Но помимо этого, как я говорил, мы переложили ответственность следить за типами на компилятор. Поэтому, например, если кто-то удалит строчки с
Лично я называю это "писать код в стиле Rust". С одной стороны - по сути это просто синтаксический сахар, реальной разницы после компиляции (вроде бы) нет. Но с другой стороны, последний вариант читаемее и безопаснее. Иногда у меня возникает ощущение, что некоторые люди очень поверхностно изучают Rust и пытаются писать "как на плюсах", расстраиваются, и возвращаются обратно. Но если бы они приложили больше усилий, то вполне возможно, их мнение было бы совсем другим.
Rust
, как и на C++
, мне бы пришлось постоянно использовать unsafe
.Возьмем
Option/optional
:Плюсы говорят "вот тип, вы можете проверять, лежит ли в нем что-то, а можете не проверять, так как уже проверяли когда-то до этого, нам пофиг. Но если не проверите, то будет бобо"
Раст говорит "вот тип, вы или должны проверить, или должны явно указать, что вы умнее через
unsafe
"И во времена, когда я был ярым C++сером, мне казалось, что это будет как в плюсах, только неудобнее, потому что я даже не подозревал, что можно лучше.
Простой пример, как в C++ проверить, что что-то лежит внутри
optional
и получить значение:if (optional_value != nullopt) {Если это в лоб переписать на Rust, получим:
auto value = *optional_value; // используем "небезопасную" штуку без проверки, но мы же молодцы, мы проверили
std::cout << value << std::endl;
}
if optional_value.is_some() {И когда я получал ответ на свой вопрос, мне казалось что как-то так оно и работает и люди реально пишут такой код
let value = unsafe { optional_value.unwrap_unchecked() };
println!("{}", value);
}
Но
Option
, и 100% у меня лежит там значение" <=> вы бы хотели вместо Option<T>
иметь тип T
. И вместо того, чтобы держать это все в голове/комментариях (как в случае C++), мы выражаем это в типах, и перекладываем ответственность следить за типами на компилятор:if let Some(value) = optional_value {В первую очередь мне нравится то, что это тупо короче и читаемее (имхо). Мы буквально написали "если в
// typeof(value) == T
println!("{}", value);
}
Option
лежит что-то, то дай значение, иначе пропусти ветку" в одну строчку.Но помимо этого, как я говорил, мы переложили ответственность следить за типами на компилятор. Поэтому, например, если кто-то удалит строчки с
if
(или переместит/скопирует тело if
), то первые два варианта нормально скомпилируются, но последний - нет (т.к. у вас просто нет переменной value
)Лично я называю это "писать код в стиле Rust". С одной стороны - по сути это просто синтаксический сахар, реальной разницы после компиляции (вроде бы) нет. Но с другой стороны, последний вариант читаемее и безопаснее. Иногда у меня возникает ощущение, что некоторые люди очень поверхностно изучают Rust и пытаются писать "как на плюсах", расстраиваются, и возвращаются обратно. Но если бы они приложили больше усилий, то вполне возможно, их мнение было бы совсем другим.
👍6😁1🤔1
Panic! At the 0xC0D3
Еще когда я даже не учил Rust, но спрашивал у знающих знакомых "как работает XXX", меня часто не удовлетворял ответ: казалось, что для того, чтобы писать такой же эффективный код на Rust, как и на C++, мне бы пришлось постоянно использовать unsafe. Возьмем…
Когда выложил этот пост, скинули статью, которая довольно хорошо объясняет подход "писать код в стиле Rust"/"type driven design"
В статье все примеры кода на хаскеле, но параллельно с этим объясняется, что он делает, поэтому я не зная хаскеля в целом все смог понять
https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
Не знаю, насколько много людей отсюда ее прочитают, но все таки решил поделиться
В статье все примеры кода на хаскеле, но параллельно с этим объясняется, что он делает, поэтому я не зная хаскеля в целом все смог понять
https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
Не знаю, насколько много людей отсюда ее прочитают, но все таки решил поделиться
👍2
Panic! At the 0xC0D3
Когда выложил этот пост, скинули статью, которая довольно хорошо объясняет подход "писать код в стиле Rust"/"type driven design" В статье все примеры кода на хаскеле, но параллельно с этим объясняется, что он делает, поэтому я не зная хаскеля в целом все смог…
А поделиться я ей решил, потому что ее суть напомнила мне об одной мысли из какого-то древнего доклада, который я не могу откопать теперь(
Если верить моей памяти, то суть его была в том, что чуваку досталась какая-то легаси кодовая база с кучей багосов, и он решил поанализировать, какого рода баги там есть
И пришел к тому, что бОльшая часть самых неприятных ошибок, это... условия. Конкретно одинаковые по смыслу условия, разбросанные по коду
Ну например, вы проверяете, что строка начинается с какого-то префикса при обработке запроса, и в какой-то внутренней функции
Основная проблема в том, что код постоянно меняется, и вы, например, поменяете префикс в одном месте, но не в другом, хоп и получили баг
По сути, есть разные категории ошибок: проезды по памяти, гонки, логические ошибки и прочее. И многие ошибки мы умеем довольно быстро находить: (safe) Rust не даст вам скомпилировать код с проездом по памяти или гонкой, всякие санитайзеры помогут найти в C++
Но вот логические ошибки, это не поймать инструментами особо. Самый лучший инструмент - это написание тестов, что как мы все знаем, довольно часто игнорируется.
Еще после просмотра того доклада, я как-то начал относиться к любым "логическим"
И вот как раз этот подход из статьи выше, как мне кажется, помогает минимизировать количество таких ошибок: нужно пытаться выносить валидацию ("логические" проверки) в одно место, и после этого в системе типов как бы помечать этот объект "провалидированым", а в функциях и прочем уже использовать этот провалидированный тип
В статье в целом есть хорошие примеры, но я все равно приведу еще один: допустим, мы пишем какую-то библиотеку с математическими функциями, и многие функции могут принимать только положительные числа.
Если это целочисленные типы, то у нас уже(!) есть встроенное решение:
Что делать, если мы получаем число от пользователя (из json реквеста, ввод с клавиатуры, не важно)? Провалидировать его как можно раньше и засунуть в
Забавно то, что если вы начнете писать библиотеку в такой идеологии, то все функции такого рода будут принимать
Если верить моей памяти, то суть его была в том, что чуваку досталась какая-то легаси кодовая база с кучей багосов, и он решил поанализировать, какого рода баги там есть
И пришел к тому, что бОльшая часть самых неприятных ошибок, это... условия. Конкретно одинаковые по смыслу условия, разбросанные по коду
Ну например, вы проверяете, что строка начинается с какого-то префикса при обработке запроса, и в какой-то внутренней функции
Основная проблема в том, что код постоянно меняется, и вы, например, поменяете префикс в одном месте, но не в другом, хоп и получили баг
По сути, есть разные категории ошибок: проезды по памяти, гонки, логические ошибки и прочее. И многие ошибки мы умеем довольно быстро находить: (safe) Rust не даст вам скомпилировать код с проездом по памяти или гонкой, всякие санитайзеры помогут найти в C++
Но вот логические ошибки, это не поймать инструментами особо. Самый лучший инструмент - это написание тестов, что как мы все знаем, довольно часто игнорируется.
Еще после просмотра того доклада, я как-то начал относиться к любым "логическим"
if
ам с осторожностью: по сути, каждый из них может быть причиной бага.И вот как раз этот подход из статьи выше, как мне кажется, помогает минимизировать количество таких ошибок: нужно пытаться выносить валидацию ("логические" проверки) в одно место, и после этого в системе типов как бы помечать этот объект "провалидированым", а в функциях и прочем уже использовать этот провалидированный тип
В статье в целом есть хорошие примеры, но я все равно приведу еще один: допустим, мы пишем какую-то библиотеку с математическими функциями, и многие функции могут принимать только положительные числа.
Если это целочисленные типы, то у нас уже(!) есть встроенное решение:
unsized int
и его вариации:fn sqrt(val: u32) -> u32
Все, нам не нужны никакие проверки внутри функции, система типов гарантирует это. И я думаю, что вы согласитесь, что да, это разумное решение и у него нет особо минусов.Что делать, если мы получаем число от пользователя (из json реквеста, ввод с клавиатуры, не важно)? Провалидировать его как можно раньше и засунуть в
unsigned int
тип, а дальше работать только с ним.Забавно то, что если вы начнете писать библиотеку в такой идеологии, то все функции такого рода будут принимать
uint
, а следовательно они как бы будут заставлять вас провалидировать значение как можно раньше:fn sqrt(val: u32) -> u32 // базовая функция
fn round_sqrt(val: u32) -> u32 // какая-то функция, которая использует первуюНо что если мы хотим еще и работать с числами с плавающей точкой? В большинстве языков нет встроенного типа для этого. Дак давайте сделаем свой!
fn handle(val: String) { // наш обработчик
let res = round_sqrt(val???); // хотим вызвать "сложную" функцию, но она сразу требует провалидированный тип
}
pub struct uf32 {По сути, это тоже самое, что и
val: f32, // приватное
}
fn new(val: f32) -> uf32 {
if val < 0.0 {
panic!("expected positive value"); // или, еще лучше, можно возвращать ошибку
}
return uf32{ val };
}
unsafe fn new_unchecked(val: f32) -> uf32 { // явно помечаем, что это небезопасно
return uf32{ val };
}
// арифм операции и прочее
uint
: чтобы его получить, (по хорошему) нужно проверить, что значение действительно соответсвует типу. И после этого, можно его использовать уже без всяких проверокfn sqrt(val: uf32) -> uf32; // без проверок
👍3
Panic! At the 0xC0D3
А поделиться я ей решил, потому что ее суть напомнила мне об одной мысли из какого-то древнего доклада, который я не могу откопать теперь( Если верить моей памяти, то суть его была в том, что чуваку досталась какая-то легаси кодовая база с кучей багосов, и…
По началу это может показаться странным. "Да никто так не делает", "это какой-то оверинжиниринг". Но по факту - это тот же
И на самом деле, нет, это используют, но больше в функциональных языках: вспомнить
"Но мой набор легаси библиотек не умеет с этим работать!" - возможно. Но это не мешает писать новые библиотеки в таком стиле.
И тут как раз хочется вернуться к
Есть ли перформанс оверхед от этого? It depends
Например, в случае
"Но я могу просто не проверять, и перенести это на плечи разработчика": да, но либо вам итак нужно будет проверять это (если это ввод от пользователя), и тогда разницы нет, будете вы проверять это в конструкторе, или внутри обработчика; либо вы на 100% уверены, что все ок, и тогда можно воспользоваться
В случае
Но это довольно простые примеры.
В реальности, зачастую это намного более сложные типы, которые показывают какие-то сложные инварианты. Самый банальный пример: парсить
Но в итоге, использовать библиотеки, написанные в
Вместо этого вам часто даже не нужна документация! Вы пишете
P. S. хотел кратко написать мысли по статье, проиграл
P. P. S. статью то все равно прочитайте!
uint
, но ufloat
, а первый используют повсеместно, чем этот тип хуже? И на самом деле, нет, это используют, но больше в функциональных языках: вспомнить
NonEmptyList
из статьи из хаскеля."Но мой набор легаси библиотек не умеет с этим работать!" - возможно. Но это не мешает писать новые библиотеки в таком стиле.
И тут как раз хочется вернуться к
Rust
: в стандартной библиотеке с давних времен есть "стандартные" решения подобных проблем: Option; Result; Box (unique_ptr), который не может быть null
- ровно по этой причине. И из-за того, что это было все (долгое?) время с языком, все библиотеки активно это используютЕсть ли перформанс оверхед от этого? It depends
Например, в случае
uf32
, вы наоборот можете выиграть в перформансе, не проверяя на отрицательные числа в каждой функции."Но я могу просто не проверять, и перенести это на плечи разработчика": да, но либо вам итак нужно будет проверять это (если это ввод от пользователя), и тогда разницы нет, будете вы проверять это в конструкторе, или внутри обработчика; либо вы на 100% уверены, что все ок, и тогда можно воспользоваться
unsafe
(который буквально показывает, что это небезопасно)В случае
NonEmptyList
(NonEmptyVec
) перформанс действительно может(!) быть хуже, потому что последовательная память, кеши и все такое.Но это довольно простые примеры.
В реальности, зачастую это намного более сложные типы, которые показывают какие-то сложные инварианты. Самый банальный пример: парсить
json
в структуру, и использовать ее везде, вместо использования абстрактного json::Value
, который бы пришлось валидировать в каждом месте (да да я смотрю на тебя python
)Но в итоге, использовать библиотеки, написанные в
type driver design
намного намного приятнее и проще. Одни флешбеки с numpy
функций после курса МО, в которых нужно заглянуть в документацию, чтобы увидеть value: int/float/array/list/object/ndarray
, передать туда какой-нибудь pandas.Series
и получить ошибкой в рантайме спустя 10 минут вычислений, потому что "а этого нет в списке извините", заставляют вздрогнуть.Вместо этого вам часто даже не нужна документация! Вы пишете
foo(bar)
, и либо оно скомпилировалось и скорее всего работает, либо нет. Помните слова "компилируется - значит работает" про Rust
, да? :)P. S. хотел кратко написать мысли по статье, проиграл
P. P. S. статью то все равно прочитайте!
👍5