bool вообще‐то уже хранится как u1, с довеском из семи бит, всегда равных нулю, существующим исключительно по технических причинам. А вот единичную систему счисления для чисел почему‐то не используют.
Вообще, если рассматривать bool как u1, то никаких разных семантик нет. Кроме того, подход, когда операторов слишком много, делает изучение языка и чтение программ ещё сложнее.
Если вам очень нужны дополнительные операторы, то для их реализации всегда можно использовать макросы. По крайней мере, так тот, кто будет читать программу, будет знать, что значение дополнительных операторов нужно начинать искать в документации на присутствующий в коде макрос. А вы сами дважды подумаете, нужно ли вам заморачиваться с не слишком лёгкими в реализации макросами, или вам достаточно будет функций.
Не понял, кто устанавливает детали вплотную и причём тут ленточка? Если плата не разведена, то нужно настраивать DRC. Если плата разведена, то ленточка не поможет: либо вы нагреваете феном/ИК и тогда после нагревания припой сам притянет компонент на место, достаточно иметь какой‐то зазор между компонентами. Либо вы паяете паяльником, тогда вам будет не до ленточки, потому что если вы не держите компонент пинцетом, то он встаёт на дыбы (я про мелкие SMD резисторы и конденсаторы). Или сдвигается паяльником куда‐то не туда.
Зачем нужны отдельно not и побитовое отрицание, если ! либо будет делать то же, что и not, либо его нельзя будет применить к булевым, а not в любом случае нельзя применить ко не‐булевым? && и || — это особый случай из‐за short-circuit и, соответственно, необходимости особого отношения со стороны компилятора.
error: expected expression, found reserved identifier `_`
--> src/main.rs:2:20
|
2 | println!("{}", _);
| ^ expected expression
error[E0425]: cannot find value `_s` in this scope
--> src/main.rs:3:20
|
3 | println!("{}", _s);
| ^^ not found in this scope
Ещё где‐то на Rust’овских форумах где‐то было обсуждение по поводу того, что let _ = foo приводит к немедленному освобождению foo, а сохранение в неиспользуемую переменную — к освобождению по выходу из контекста, из‐за чего что‐то работало неправильно.
Кроме того, т.к. в начале скрипта написано #!/bin/bash, то в этом скрипте можно просто использовать shopt -s nullglob и никаких проблем с пустыми каталогами не будет.
Насколько мне известно, в lua можно спокойно все переносы строк заменять на пробелы (с обычным исключением в виде многострочных строковых литералов). Вот ещё и пробелы убрать часто будет нельзя, но сделать программу для удаления тех, каких можно, довольно легко.
А почему, кстати, вы говорите именно про переносы строк? Перенос строки, пробел, точка с запятой или ещё какой разделитель все занимают по одному байту, а вам важно уменьшить число служебных символов в принципе (чему больше мешает do…end вместо фигурных скобок, чем какие‐то там новые строки), а не конкретно переносов строк.
Один нужно как‐то перенацеливать. Перед массивом можно поставить оптику так, чтобы разные лазеры в результате стреляли в разные стороны. Второе работает намного быстрее и меньше изнашивается.
Потому что для того, чтобы stdout был перенаправлен в title.html оболочке нужно сначала настроить stdout, а потом запустить exec, чтобы заменить себя на cat: cat не может настроить сам себе stdout, о > title.html знает только оболочка, это не аргумент cat. Т.к. в данном случае вы попросили обрезать выходной файл, то последовательность действий выглядит примерно так:
Оболочка клонирует себя, создавай дочерний процесс.
В дочернем процессе настраивается stdout: открывается файл title.html (сразу с обрезанием), закрывается старый stdout, открытый файл становится новым stdout.
С настроенным stdout дочерний процесс, бывший клоном оболочки, заменяет себя на cat.
cat читает файл и выдаёт его в stdout, но файл‐то уже обрезан!
Точная последовательность и реализация зависит от оболочки, некоторые оболочки почему‐то даже предпочитают сохранить старый stdout куда‐то, заменить его на новый и только потом клонировать себя, после завершения же дочернего процесса закрыть новый stdout и вернуть старый на место. Если хотите посмотреть, как делает ваша оболочка, то запустите strace $SHELL -c 'cat title.html > title.html'.
Но в любом случае, вы не сможете получить отличного поведения никаким образом, пока > title.html не станет аргументом, который интерпретирует cat (точнее, ваша замена cat, потому как сам cat такого не умеет): оболочка просто не может обрезать файл после чтения (которое ещё и будет выполняться кусками вперемешку с записью), а использование временных файлов или чего‐то в этом роде противоречит спецификации конструкции перенаправления вывода.
Тупо non‐reentrant VI? А где хранятся данные? В контролах или сдвиговых регистрах?
В сдвиговых регистрах. Хранить что‐то в контролах неудобно. Но в тесте была именно action engine — т.е. все операции типа «считать, изменить, записать» должны происходить внутри VI — ещё одна причина использовать всё же DVR, хоть они и медленнее. Защищать доступ к VI семафорами смысла не имеет — семафоры в LabVIEW очень, очень медленные.
Варианту же все равно надо указывать какой тип надо десериализировать, так же ж?
Variant выдаст вам ошибку, если вы десериализовываете не то, что сериализовали. Точнее, если вы десериализовываете во что‐то, что не может быть неявно приведено из того, что вы десериализовали.
Я ж енумом выбираю как десерализировать и обрабатывать данные, какие еще ошибки?
Данные сериализуются и отправляются в одном месте, а десериализовываются и обрабатываются в другом. Если вы сериализовали не то, что ожидается для данного значения enum, то будет ошибка. А т.к. это комбинация enum+variant, а не ADT, и компилятор ничего не проверяет, то вы вполне можете так сделать.
Если вы сериализуете в LabVIEW с помощью flatten to string с выкидыванием типа (самый простой способ получить сериализацию в бинарный формат, я именно так сохраняю в бинарный журнал), то при десериализации на другом конце ошибок в принципе не будет, если десериализовывать стандартными средствами, или возможность детектирования того, что что‐то пришло не то, будет ограничена (можно вручную проверить размер, иногда присутствие определённого шаблона в enum, но не более того — сериализованный таким образом кластер с четырьмя байтами от одного целого вы никогда не отличите).
Нет. В СУВТ переключатель хранится отдельно от значений. Кстати, в СУВТ и структуры можно вставлять, и юнионы, и указатели, и даже другие СУВТ.
Отдельно или нет, в структуре struct { enum TypeEnum { a, b, c } type; union { double a; int64_t b; uint64_t c; } }type без #pragma pack займёт не менее 8 байт из‐за выравнивания. Если СУВТ означает не структуру с union и enum, а что‐то ещё вроде struct { enum TypeEnum type; void *data; }, то это как‐то неоптимально, а б64 для type начинает выглядеть странно.
Кстати, в LabVIEW есть аналог сишного указателя (data value reference) — очень удобно шарить данные в параллельных VI и участках схем, при этом атомарность операций доступа к данным обеспечивается in place element structure, необходимой для доступу к ним. Советую попробовать.
DVR из‐за этой атомарности не слишком хороший аналог указателя: слишком тормозит на блокировках. Лучше, чем ничего, но я как‐то проверял параллельный доступ на запись с разной реализацией «глобальных» переменных. Всех побила реализация типа «action engine» с приоритетом subroutine и non‐reentrant типом исполнения. Учитывая, что non‐reentrant тип исполнения — это по сути та же критическая секция (и, кстати, DVR бил всё остальное до того, как я догадался проверить ускорение от subroutine), то DVR могли бы и не проигрывать.
Это меня сильно расстроило, т.к. я к тому времени в своём проекте уже избавился от всего подряд глобального (т.е. non‐reentrant VI и глобальных переменных в обоих видах) и вводить что‐то из этого обратно совершенно не собирался. И да, я использую DVR. В каком‐то из LabVIEW в DVR даже добавили возможность параллельного чтения (т.е. два разных потока могут читать данные из одного DVR одновременно; записать, конечно, ни один не может).
Хорошее замечание. Я в Лабвью решаю эту проблему комбинацией енума и сериализированных в строку данных, которые потом десериализируются и обрабатываются в зависимости от значения енума. Вручную делать это неудобно и в Метапроге я для подобных случаев делаю СУВТ.
При использовании variant вы получите ошибку в runtime и довольно быструю сериализацию. Если сериализовывать чем‐то вроде flatten to string (тот же variant, но можно выкинуть тип), то вы сэкономите память, но ошибку при посылке не того типа можно и не получить. Если сериализовывать чем‐то вроде JSON, то ошибку вы получите, но расходы на (де)сериализацию сильно увеличатся, т.к. variant ближе к представлению в памяти.
Здесь СУВТ — это структура из беззнакового 64-битного (б64) переключателя и юниона из трех возможных типов. Значению переключателя 17 соответствует беззнаковый 8-битный (б8) тип, значению переключателя 86 — 64-битная дробь, он же float/double, значению переключателя 9 — знаковое 64-битное. Обратите внимание, что в отличие от Лабвью знаковые типы обозначаются отдельным от беззнаковых цветом (светло-голубым).
А почему 64‐битного? Это для любой СУВТ так или здесь дело в том, что из‐за присутствия д64 и з64 переключатель будет в любом случае занимать по сути 8 байт из‐за выравнивания и метапрог просто не даёт об этом факте забыть?
Это ужас. Когда такое ввели? Я толком не пользовался Лабвью после 2013, ради совместимости с ХР.
В 2017. В 2018 ещё добавили type specialization structure, позволяющую иметь несколько вариантов диаграм. Разумеется, в духе утиной типизации вместо того, чтобы сделать способ описания типа в переключателе диаграммы выбор конкретного варианта осуществляется путём перебора всех вариантов по порядку, пока какой‐то из них не скомпилируется. Конкретные ошибки компиляции у mallable VI вы в 2018 не увидите: сведения об ошибке ограничиваются тем, что LabVIEW не находит подходящий вариант.
В общем, mallable VI полезные и я их часто использую, но выглядят они как будто программисты решали задачу «нам нужны generic’и — давайте сделаем их как можно проще, чтобы сделать их как можно быстрее к релизу».
Удивляюсь как можно так тупить вместо того, чтобы придумать СУВТ. Да и вообще сделать LabVIEW NXG не на самом LabVIEW, а на какой-то унылой скриптухе от M$ — чистейшей воды идиотизм.
Вообще‐то далеко не самый плохой выбор для UI, сама LabVIEW в этом отношении была бы хуже. И, насколько я понял, это переделка в первую очередь UI и формата хранения VI — и то, и то всё равно нужно было сделать давно, а то что это за графический язык без zoom, зато с IDE, которая падает, если падает компилятор, и который сохраняется в формате, совершенно неудобном для использования с VCS. Компилятор у NXG вроде остался тот же, на основе LLVM.
В LabVIEW типизация статическая, но не строгая — за неявное приведение типов компилятор по рукам не даст. Хорошо хоть красную точку покажет, и позволит поискать VI с такими точками. Иногда раздражает, но красной точки обычно достаточно, чтобы выявить баги.
В общем, с типизацией для большинства случаев всё в LabVIEW неплохо, пока не вылезает (у меня) один из двух use case:
Обмен командами между VI по очередям означает постоянную возню с variant и фактически динамической типизацией. При условии, конечно, что вам нужно обмениваться командами, к которым прилагаются разные данные. В Rust такое легко решается ADT. В C такое не так легко (в смысле, компилятор по рукам когда надо не даст) решается комбинацией из enum и union.
Сравнительно недавние попытки LabVIEW в generic’и (то, что называется mallable VI) часто приходятся к месту. Но у них плохо с проверкой типов, а что VI ожидает на вход можно понять только по документации. Получать на вход условно struct<T> { size: usize, array: [T] } с возможностью менять только T, а не всё подряд, было бы удобнее. А сейчас в mallable VI по сути утиная типизация, и уже без «красных точек» где бы то ни было.
И? Я не писал про перевод diff обратно в картинки.
А с тем, что картинки будут переведены во что‐то ещё вы ничего не сделаете. И лучше чтобы «что‐то ещё» было именно текстом, из которого получается более‐менее понятный diff, иначе работать с любыми VCS к интерфейсу которого вы не приляпали свой графический diff (кстати, из графических языков кто‐то так вообще сделал? Я максимум что видел — возможность вызвать этот графический diff из того же tortoisehg.) попросту неудобно.
Где я написал про «перевод текста обратно в картинки»? У меня в первом абзаце две идеи «как бы я сделал формат графического файла так, чтобы простой diff можно понять» и «как бы я делал diff в графическом интерфейсе». Картинку, описанную в последнем предложении первого абзаца, из diff’а и двух сравниваемых файлов получить можно, но, действительно, зачем? Я такого не писал.
И, кстати, «непрофессионалы» от такой организации представления данных ничего не потеряют, кроме увеличения объёма файлов.
Вменяемый diff сделать в моём понимании не так уж сложно. В простейшем случае вы просто пишете в текстовом файле в абсолютно разные места сортированные по местонахождению список узлов (буквально ID и тип узла), список логических соединений между узлами, список «логических» расположений (актуально для LabVIEW, где есть вещи вида case structure, внутри которых находится что‐то ещё), список «физических» расположений (т.е. координат), остальные метаданные. При правильно выбранных идентификаторах по простому текстовому diff уже можно будет понять, что поменялось. Не‐текстовое представление немного сложнее, но просто две панели с подсветкой вида «этого узла/проводника нет на другой панели» (плюс «внутри этой структуры есть подсветка») должны сойти в большинстве случаев.
Вот сделать слияние так, чтобы результат не выглядел отвратно — это уже будет намного сложнее. Кроме того, мне совершенно не нравится, как на LabVIEW выглядит кодогенерация, или любой вид рефакторинга, который нельзя сделать стандартными средствами (а тот, что таки можно сделать стандартными средствами, ещё потом часто придётся самому облагораживать). Не думаю, что другие графические языки тут будут сильно отличаться.
И все эти проблемы возникают не столько оттого, что в графических языках в принципе нельзя выполнить сравнение/слияние/рефакторинг/кодогенерацию/…, а в том, что, чтобы пользователь мог нормально пользоваться результатом разработчику нужно потратить слишком много усилий. А сами языки ориентированы не на профессиональных программистов, которые такие усилия оценят больше.
Та часть, которая собственно занимается разбором одной записи из одного журнала, в Python и Rust по сути не сильно отличается — придумать какой‐то особенный способ разбора бинарных журналов сложно. Статическая типизация сильно помогала с разбором записей вида «время, тип, данные», где данные зависят от типа — скриптом на LabVIEW я делал из LabVIEW’шного enum Rust’овый enum (до этого — Python’овский), потом Rust не давал мне забыть обработать какой‐то вариант. Данные запихивались в другой enum (который уже ADT, а не просто набор именованных целых чисел) и при создании функции для отладочной печати оного Rust опять не давал мне забыть о том, что в этом enum есть какой‐то вариант, или о том, что этот вариант содержит какое‐то значение.
Ну и плюс в том, что вариант «загнать все данные из всех журналов в один большой список и отсортировать» мне почему‐то показался неестественным. Я не помню точно, о чём именно я думал в то время, но мне либо не пришли в голову варианты реализации на Rust, либо я уже подумывал о таком решении для Python, но решил не заморачиваться, т.к. при использовании CPython в случае отсутствия большого списка я выиграл бы на выделении памяти под него и на его сортировке. А проблемы вида «доступ к любым данным осуществляется только через несколько прыжков по указателям» и «для создания записи нужно выделить память под каждое из полей записи» никуда бы не делись. И эти проблемы в языке с динамической типизацией решить сложно. Возможно, помог бы PyPy с его JIT, но PyPy всё равно не стал бы давать по рукам за забытый вариант enum.
Я как‐то парсил двоичные журналы размером порядка 100—10 000 МБ, используя не сильно оптимизированный код на Python (struct использовал, но на этом всё). Получил времена исполнения порядка 10 минут на запуск и костыли вида «распарсить журнал, сериализовать результат pickle (чтобы при изменении следующего шага не парсить журнал ещё раз), потом уже что‐то сделать с журналом (со сбросом промежуточных данных на диск, чтобы опять же не повторять шаги)». В итоге плюнул и переписал на так же не особо оптимизированный Rust, время исполнения уменьшилось где‐то на два порядка.
Я, правда, сейчас думаю, что если бы я сначала попытался на Python применить некоторые вещи, которые я использовал с самого начала на Rust*, то, возможно, я выиграл бы порядок и на нём, но вряд ли два. Кроме того, я эти вещи использовал с самого начала, потому что попытка на Rust сделать «как на Python» выглядело бы неестественно. Дополнительно, Rust не позволяет мне забывать обработать варианты enum, а это была частая причина перезапуска, когда я писал на Python.
Я это к чему: имеющаяся типизация в Rust для меня не только сократила количество перезапусков обработчика журналов, но и сделала более оптимальный код более естественным. Я думаю, подобный результат получился бы на любом языке, на котором есть ADT и классические структуры, просто именно Rust был на слуху, и я на нём уже что‐то для пробы писал.
* А именно, не пытаться загнать все журналы в память, чтобы их вместе отсортировать по временной метке, а читать из журналов в правильном порядке, храня в памяти только одну запись из одного журнала и состояние.
Ну да, одиночные сбои я на работе регулярно наблюдаю, иногда даже большими пачками, а инвалидацию итератора никогда не видел :)
(Не пишу на C++ и занимаюсь испытаниями на радиационную стойкость. В коде на Rust почему‐то инвалидации не происходит, в коде на LabVIEW, C и ассемблере тоже…)
cat file | grep A быстро превращается при нужде в cat file | less или cat file | sed | grep A. Превращать grep A file во что‐то из списка — это как раз будет упражнение для спецолимпиады — сделать можно, но с первым вариантов выйдет быстрее. Особенно, если вы предварительно смотрите содержимое файла, в котором будете искать, с помощью просто cat file, а не less. Ещё иногда есть соображения вида «я часто ищу разные вещи в одном файле, тогда лучше иметь в истории то, что позволяет легче менять поисковый запрос».
pgrep C | xargs kill вполне себе имеет смысл, если вы сначала хотите посмотреть, не прибьёте ли вы лишние процессы (pgrep C -a), а только потом уже прибить — замена -a в конце предыдущей команды на | xargs kill -9 у меня займёт примерно то же время, что и удаление -a и замена pgrep в начале на pkill. Правда, я лично в этом случае всё же буду менять ps -C на killall (а потом ругаться, если чего‐то из этого нет и менять на pgrep -e/pkill -e), но эта «копипаста» вполне объяснима и часто имеет отношение не к «cargo cult», а к тому, что это не первая команда, которую вы набрали в консоли.
Или к тому, что обычно вы набираете в консоли именно так по описанным причинам, поэтому такой вид просто становится привычным.
bool
вообще‐то уже хранится какu1
, с довеском из семи бит, всегда равных нулю, существующим исключительно по технических причинам. А вот единичную систему счисления для чисел почему‐то не используют.Вообще, если рассматривать
bool
какu1
, то никаких разных семантик нет. Кроме того, подход, когда операторов слишком много, делает изучение языка и чтение программ ещё сложнее.Если вам очень нужны дополнительные операторы, то для их реализации всегда можно использовать макросы. По крайней мере, так тот, кто будет читать программу, будет знать, что значение дополнительных операторов нужно начинать искать в документации на присутствующий в коде макрос. А вы сами дважды подумаете, нужно ли вам заморачиваться с не слишком лёгкими в реализации макросами, или вам достаточно будет функций.
Не понял, кто устанавливает детали вплотную и причём тут ленточка? Если плата не разведена, то нужно настраивать DRC. Если плата разведена, то ленточка не поможет: либо вы нагреваете феном/ИК и тогда после нагревания припой сам притянет компонент на место, достаточно иметь какой‐то зазор между компонентами. Либо вы паяете паяльником, тогда вам будет не до ленточки, потому что если вы не держите компонент пинцетом, то он встаёт на дыбы (я про мелкие SMD резисторы и конденсаторы). Или сдвигается паяльником куда‐то не туда.
А что такое «анмэндж/нативный код» у Rust?
Зачем нужны отдельно
not
и побитовое отрицание, если!
либо будет делать то же, что иnot
, либо его нельзя будет применить к булевым, аnot
в любом случае нельзя применить ко не‐булевым?&&
и||
— это особый случай из‐за short-circuit и, соответственно, необходимости особого отношения со стороны компилятора.Это не переменная: попробуйте скомпилировать
Сразу видно, что
_s
— переменная, а_
— нет.Ещё где‐то на Rust’овских форумах где‐то было обсуждение по поводу того, что
let _ = foo
приводит к немедленному освобождениюfoo
, а сохранение в неиспользуемую переменную — к освобождению по выходу из контекста, из‐за чего что‐то работало неправильно.Кроме того, т.к. в начале скрипта написано
#!/bin/bash
, то в этом скрипте можно просто использоватьshopt -s nullglob
и никаких проблем с пустыми каталогами не будет.Насколько мне известно, в lua можно спокойно все переносы строк заменять на пробелы (с обычным исключением в виде многострочных строковых литералов). Вот ещё и пробелы убрать часто будет нельзя, но сделать программу для удаления тех, каких можно, довольно легко.
А почему, кстати, вы говорите именно про переносы строк? Перенос строки, пробел, точка с запятой или ещё какой разделитель все занимают по одному байту, а вам важно уменьшить число служебных символов в принципе (чему больше мешает
do…end
вместо фигурных скобок, чем какие‐то там новые строки), а не конкретно переносов строк.Один нужно как‐то перенацеливать. Перед массивом можно поставить оптику так, чтобы разные лазеры в результате стреляли в разные стороны. Второе работает намного быстрее и меньше изнашивается.
Потому что для того, чтобы stdout был перенаправлен в title.html оболочке нужно сначала настроить stdout, а потом запустить exec, чтобы заменить себя на cat: cat не может настроить сам себе stdout, о
> title.html
знает только оболочка, это не аргументcat
. Т.к. в данном случае вы попросили обрезать выходной файл, то последовательность действий выглядит примерно так:cat
.cat
читает файл и выдаёт его в stdout, но файл‐то уже обрезан!Точная последовательность и реализация зависит от оболочки, некоторые оболочки почему‐то даже предпочитают сохранить старый stdout куда‐то, заменить его на новый и только потом клонировать себя, после завершения же дочернего процесса закрыть новый stdout и вернуть старый на место. Если хотите посмотреть, как делает ваша оболочка, то запустите
strace $SHELL -c 'cat title.html > title.html'
.Но в любом случае, вы не сможете получить отличного поведения никаким образом, пока
> title.html
не станет аргументом, который интерпретируетcat
(точнее, ваша заменаcat
, потому как самcat
такого не умеет): оболочка просто не может обрезать файл после чтения (которое ещё и будет выполняться кусками вперемешку с записью), а использование временных файлов или чего‐то в этом роде противоречит спецификации конструкции перенаправления вывода.В сдвиговых регистрах. Хранить что‐то в контролах неудобно. Но в тесте была именно action engine — т.е. все операции типа «считать, изменить, записать» должны происходить внутри VI — ещё одна причина использовать всё же DVR, хоть они и медленнее. Защищать доступ к VI семафорами смысла не имеет — семафоры в LabVIEW очень, очень медленные.
Variant выдаст вам ошибку, если вы десериализовываете не то, что сериализовали. Точнее, если вы десериализовываете во что‐то, что не может быть неявно приведено из того, что вы десериализовали.
Данные сериализуются и отправляются в одном месте, а десериализовываются и обрабатываются в другом. Если вы сериализовали не то, что ожидается для данного значения enum, то будет ошибка. А т.к. это комбинация enum+variant, а не ADT, и компилятор ничего не проверяет, то вы вполне можете так сделать.
Если вы сериализуете в LabVIEW с помощью flatten to string с выкидыванием типа (самый простой способ получить сериализацию в бинарный формат, я именно так сохраняю в бинарный журнал), то при десериализации на другом конце ошибок в принципе не будет, если десериализовывать стандартными средствами, или возможность детектирования того, что что‐то пришло не то, будет ограничена (можно вручную проверить размер, иногда присутствие определённого шаблона в enum, но не более того — сериализованный таким образом кластер с четырьмя байтами от одного целого вы никогда не отличите).
Отдельно или нет, в структуре
struct { enum TypeEnum { a, b, c } type; union { double a; int64_t b; uint64_t c; } }
type
без#pragma pack
займёт не менее 8 байт из‐за выравнивания. Если СУВТ означает не структуру с union и enum, а что‐то ещё вродеstruct { enum TypeEnum type; void *data; }
, то это как‐то неоптимально, а б64 дляtype
начинает выглядеть странно.DVR из‐за этой атомарности не слишком хороший аналог указателя: слишком тормозит на блокировках. Лучше, чем ничего, но я как‐то проверял параллельный доступ на запись с разной реализацией «глобальных» переменных. Всех побила реализация типа «action engine» с приоритетом subroutine и non‐reentrant типом исполнения. Учитывая, что non‐reentrant тип исполнения — это по сути та же критическая секция (и, кстати, DVR бил всё остальное до того, как я догадался проверить ускорение от subroutine), то DVR могли бы и не проигрывать.
Это меня сильно расстроило, т.к. я к тому времени в своём проекте уже избавился от всего подряд глобального (т.е. non‐reentrant VI и глобальных переменных в обоих видах) и вводить что‐то из этого обратно совершенно не собирался. И да, я использую DVR. В каком‐то из LabVIEW в DVR даже добавили возможность параллельного чтения (т.е. два разных потока могут читать данные из одного DVR одновременно; записать, конечно, ни один не может).
При использовании variant вы получите ошибку в runtime и довольно быструю сериализацию. Если сериализовывать чем‐то вроде flatten to string (тот же variant, но можно выкинуть тип), то вы сэкономите память, но ошибку при посылке не того типа можно и не получить. Если сериализовывать чем‐то вроде JSON, то ошибку вы получите, но расходы на (де)сериализацию сильно увеличатся, т.к. variant ближе к представлению в памяти.
А почему 64‐битного? Это для любой СУВТ так или здесь дело в том, что из‐за присутствия д64 и з64 переключатель будет в любом случае занимать по сути 8 байт из‐за выравнивания и метапрог просто не даёт об этом факте забыть?
В 2017. В 2018 ещё добавили type specialization structure, позволяющую иметь несколько вариантов диаграм. Разумеется, в духе утиной типизации вместо того, чтобы сделать способ описания типа в переключателе диаграммы выбор конкретного варианта осуществляется путём перебора всех вариантов по порядку, пока какой‐то из них не скомпилируется. Конкретные ошибки компиляции у mallable VI вы в 2018 не увидите: сведения об ошибке ограничиваются тем, что LabVIEW не находит подходящий вариант.
В общем, mallable VI полезные и я их часто использую, но выглядят они как будто программисты решали задачу «нам нужны generic’и — давайте сделаем их как можно проще, чтобы сделать их как можно быстрее к релизу».
Вообще‐то далеко не самый плохой выбор для UI, сама LabVIEW в этом отношении была бы хуже. И, насколько я понял, это переделка в первую очередь UI и формата хранения VI — и то, и то всё равно нужно было сделать давно, а то что это за графический язык без zoom, зато с IDE, которая падает, если падает компилятор, и который сохраняется в формате, совершенно неудобном для использования с VCS. Компилятор у NXG вроде остался тот же, на основе LLVM.
В LabVIEW типизация статическая, но не строгая — за неявное приведение типов компилятор по рукам не даст. Хорошо хоть красную точку покажет, и позволит поискать VI с такими точками. Иногда раздражает, но красной точки обычно достаточно, чтобы выявить баги.
В общем, с типизацией для большинства случаев всё в LabVIEW неплохо, пока не вылезает (у меня) один из двух use case:
struct<T> { size: usize, array: [T] }
с возможностью менять толькоT
, а не всё подряд, было бы удобнее. А сейчас в mallable VI по сути утиная типизация, и уже без «красных точек» где бы то ни было.И? Я не писал про перевод diff обратно в картинки.
А с тем, что картинки будут переведены во что‐то ещё вы ничего не сделаете. И лучше чтобы «что‐то ещё» было именно текстом, из которого получается более‐менее понятный diff, иначе работать с любыми VCS к интерфейсу которого вы не приляпали свой графический diff (кстати, из графических языков кто‐то так вообще сделал? Я максимум что видел — возможность вызвать этот графический diff из того же tortoisehg.) попросту неудобно.
Где я написал про «перевод текста обратно в картинки»? У меня в первом абзаце две идеи «как бы я сделал формат графического файла так, чтобы простой diff можно понять» и «как бы я делал diff в графическом интерфейсе». Картинку, описанную в последнем предложении первого абзаца, из diff’а и двух сравниваемых файлов получить можно, но, действительно, зачем? Я такого не писал.
И, кстати, «непрофессионалы» от такой организации представления данных ничего не потеряют, кроме увеличения объёма файлов.
Вменяемый diff сделать в моём понимании не так уж сложно. В простейшем случае вы просто пишете в текстовом файле в абсолютно разные места сортированные по местонахождению список узлов (буквально ID и тип узла), список логических соединений между узлами, список «логических» расположений (актуально для LabVIEW, где есть вещи вида case structure, внутри которых находится что‐то ещё), список «физических» расположений (т.е. координат), остальные метаданные. При правильно выбранных идентификаторах по простому текстовому diff уже можно будет понять, что поменялось. Не‐текстовое представление немного сложнее, но просто две панели с подсветкой вида «этого узла/проводника нет на другой панели» (плюс «внутри этой структуры есть подсветка») должны сойти в большинстве случаев.
Вот сделать слияние так, чтобы результат не выглядел отвратно — это уже будет намного сложнее. Кроме того, мне совершенно не нравится, как на LabVIEW выглядит кодогенерация, или любой вид рефакторинга, который нельзя сделать стандартными средствами (а тот, что таки можно сделать стандартными средствами, ещё потом часто придётся самому облагораживать). Не думаю, что другие графические языки тут будут сильно отличаться.
И все эти проблемы возникают не столько оттого, что в графических языках в принципе нельзя выполнить сравнение/слияние/рефакторинг/кодогенерацию/…, а в том, что, чтобы пользователь мог нормально пользоваться результатом разработчику нужно потратить слишком много усилий. А сами языки ориентированы не на профессиональных программистов, которые такие усилия оценят больше.
Та часть, которая собственно занимается разбором одной записи из одного журнала, в Python и Rust по сути не сильно отличается — придумать какой‐то особенный способ разбора бинарных журналов сложно. Статическая типизация сильно помогала с разбором записей вида «время, тип, данные», где данные зависят от типа — скриптом на LabVIEW я делал из LabVIEW’шного enum Rust’овый enum (до этого — Python’овский), потом Rust не давал мне забыть обработать какой‐то вариант. Данные запихивались в другой enum (который уже ADT, а не просто набор именованных целых чисел) и при создании функции для отладочной печати оного Rust опять не давал мне забыть о том, что в этом enum есть какой‐то вариант, или о том, что этот вариант содержит какое‐то значение.
Ну и плюс в том, что вариант «загнать все данные из всех журналов в один большой список и отсортировать» мне почему‐то показался неестественным. Я не помню точно, о чём именно я думал в то время, но мне либо не пришли в голову варианты реализации на Rust, либо я уже подумывал о таком решении для Python, но решил не заморачиваться, т.к. при использовании CPython в случае отсутствия большого списка я выиграл бы на выделении памяти под него и на его сортировке. А проблемы вида «доступ к любым данным осуществляется только через несколько прыжков по указателям» и «для создания записи нужно выделить память под каждое из полей записи» никуда бы не делись. И эти проблемы в языке с динамической типизацией решить сложно. Возможно, помог бы PyPy с его JIT, но PyPy всё равно не стал бы давать по рукам за забытый вариант enum.
Я как‐то парсил двоичные журналы размером порядка 100—10 000 МБ, используя не сильно оптимизированный код на Python (
struct
использовал, но на этом всё). Получил времена исполнения порядка 10 минут на запуск и костыли вида «распарсить журнал, сериализовать результат pickle (чтобы при изменении следующего шага не парсить журнал ещё раз), потом уже что‐то сделать с журналом (со сбросом промежуточных данных на диск, чтобы опять же не повторять шаги)». В итоге плюнул и переписал на так же не особо оптимизированный Rust, время исполнения уменьшилось где‐то на два порядка.Я, правда, сейчас думаю, что если бы я сначала попытался на Python применить некоторые вещи, которые я использовал с самого начала на Rust*, то, возможно, я выиграл бы порядок и на нём, но вряд ли два. Кроме того, я эти вещи использовал с самого начала, потому что попытка на Rust сделать «как на Python» выглядело бы неестественно. Дополнительно, Rust не позволяет мне забывать обработать варианты enum, а это была частая причина перезапуска, когда я писал на Python.
Я это к чему: имеющаяся типизация в Rust для меня не только сократила количество перезапусков обработчика журналов, но и сделала более оптимальный код более естественным. Я думаю, подобный результат получился бы на любом языке, на котором есть ADT и классические структуры, просто именно Rust был на слуху, и я на нём уже что‐то для пробы писал.
* А именно, не пытаться загнать все журналы в память, чтобы их вместе отсортировать по временной метке, а читать из журналов в правильном порядке, храня в памяти только одну запись из одного журнала и состояние.
Ну да, одиночные сбои я на работе регулярно наблюдаю, иногда даже большими пачками, а инвалидацию итератора никогда не видел :)
(Не пишу на C++ и занимаюсь испытаниями на радиационную стойкость. В коде на Rust почему‐то инвалидации не происходит, в коде на LabVIEW, C и ассемблере тоже…)
cat file | grep A
быстро превращается при нужде вcat file | less
илиcat file | sed | grep A
. Превращатьgrep A file
во что‐то из списка — это как раз будет упражнение для спецолимпиады — сделать можно, но с первым вариантов выйдет быстрее. Особенно, если вы предварительно смотрите содержимое файла, в котором будете искать, с помощью простоcat file
, а неless
. Ещё иногда есть соображения вида «я часто ищу разные вещи в одном файле, тогда лучше иметь в истории то, что позволяет легче менять поисковый запрос».pgrep C | xargs kill
вполне себе имеет смысл, если вы сначала хотите посмотреть, не прибьёте ли вы лишние процессы (pgrep C -a
), а только потом уже прибить — замена-a
в конце предыдущей команды на| xargs kill -9
у меня займёт примерно то же время, что и удаление-a
и заменаpgrep
в начале наpkill
. Правда, я лично в этом случае всё же буду менятьps -C
наkillall
(а потом ругаться, если чего‐то из этого нет и менять наpgrep -e
/pkill -e
), но эта «копипаста» вполне объяснима и часто имеет отношение не к «cargo cult», а к тому, что это не первая команда, которую вы набрали в консоли.Или к тому, что обычно вы набираете в консоли именно так по описанным причинам, поэтому такой вид просто становится привычным.