Pull to refresh
2
1.2

Специалист по теории типов USB-кабелей

Send message

Реализуется указателями на thunk'и. x = 0:y означает, упрощая, что в памяти создаётся cons-cell (которая сама по себе держит два указателя), где первый (head) указатель указывает на 0, а второй (tail) — на y. Аналогично y — это cons-cell, где первый указатель указывает на 1, а второй — на x. Так как : — ленивый конструктор, то вычислять его аргументы не нужно, поэтому всё работает.

Для того, чтобы иметь такой же в абсолютных величинах достаток, как 100 лет назад, вам сегодня нужно работать сильно меньше восьми часов в сутки.

А вот если нужно чтобы и сделать и быстро?

Тогда пишите на JS.

А, вы имели в виду рантайм-поведение? Тогда почему (компилтайм-)блокирование, (компилтайм-)локи для вас влияют на что-то в рантайме?

Я уж не говорю о том, что более ограниченная семантика языка помогает компилятору оптимизировать код.

Ну или изучать матчасть (параллельное программирование и т.д.).

Жаль, что матчасть никто выучить не способен. Вон, из недавнего:

В загрузчике GRUB2 выявлена 21 уязвимость

Выявленные уязвимости:

  • CVE-2024-45774: запись за границу буфера при разборе специально оформленных JPEG-изображений.

  • CVE-2024-45776, CVE-2024-45777: целочисленные переполнения при чтении специально оформленных mo-файлов, приводящие к записи за пределы буфера.

  • CVE-2024-45778, CVE-2024-45779: целочисленные переполнения при работе с повреждённой файловой системой BFS, приводящие к переполнению буфера.

  • CVE-2024-45780: целочисленное переполнение при обработке специально оформленных tar-архивов, приводящее к записи за пределы буфера.

  • CVE-2024-45781, CVE-2025-0677: переполнения буфера при работе с повреждённой файловой системой UFS.

  • CVE-2024-45782, CVE-2025-1125: переполнения буфера при монтировании специально оформленного раздела HFS.

  • CVE-2025-0622: обращение к памяти после её освобождения при манипуляциях с модулями, может привести к выполнению кода атакующего.

  • CVE-2025-0624: переполнение буфера при сетевой загрузке.

  • CVE-2025-0678: переполнения буфера при работе с повреждённой файловой системой Squash4.

  • CVE-2025-0684: переполнения буфера при манипуляциях с символическими ссылками в ФС Reiserfs.

  • CVE-2025-0685: переполнения буфера при манипуляциях с символическими ссылками в ФС JFS.

  • CVE-2025-0685: переполнения буфера при манипуляциях с символическими ссылками в ФС ROMFS.

  • CVE-2025-0689: переполнения буфера при работе со специально модифицированным разделом UDF.

  • CVE-2025-0690: переполнения буфера при получении специально оформленных данных с клавиатуры.

  • CVE-2025-1118: обход режима изоляции Lockdown и извлечение произвольного содержимого памяти через выполнение команды dump.

  • CVE-2024-45775: отсутствие проверки кода ошибки при выделении памяти при разборе переданных аргументов может привести к повреждению данных IVT (Interrupt Vector Table).

  • CVE-2024-45783: доступ по нулевому указателю при монтировании некорректной ФС HFS+.

Всё как всегда. Всё как мы любим в сишке. И это всё в

15:30:18 деанон@деанон ~/Programming/tmp/grub (master) % cloc . | head
github.com/AlDanial/cloc v 2.00  T=0.62 s (2549.4 files/s, 737694.3 lines/s)
---------------------------------------------------------------------------------------
Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
C                                      843          42453          32782         245992
C/C++ Header                           494           8234          16649          40293
Text                                     7             20              0          24738

за год на менее чем 300к строк кода — по промышленным меркам милипусечный проект.

Какое параллельное программирование? Массивы и их индексацию бы освоить.

Это (Middle) будет именно ячейкой с двумя указателями.

Другое дело, что он будет очень жёстко иммутабельный: у вас не получится его поменять (даже добавить узел в начало или в конец) без пересоздания вообще всей структуры. По крайней мере, именно в таком виде (я не могу сходу придумать способ это обойти иммутабельным образом, но и не могу строго доказать, что его не существует).

Лень позволяет использовать объект до его полного определения. В хаскель-тусовке это называется tying the knot, и сводится не только к двусвязным спискам, но и, например, к деревьям, где у каждого узла есть и (указатель на) родитель, и дети.

Перекат в Европу хорош, если вы ниже примерно среднего (и тогда вам все эти социальные гарантии важны и полезны), и плох (на фоне переката в США, скажем), если вы выше среднего (когда все эти социальные гарантии только мешают и ограничивают). Ну, просто потому, что в последних, например, доступны кратно более высокие зарплаты, с которых вы ещё более кратно больше получите на руки, и вопроса «а сколько у меня отпуска в год» не стоит вообще потому, что можно позволить себе регулярно год-два не работать вообще.

Почти все imagebin-сервисы (особенно из всяких списков вроде «клоны pomf.cat») закрываются ровно по этим причинам.

Ну и пусть. Если программист пишет новый класс extends Order, значит ему так надо для его задачи.

Если программист делает ошибки, значит ему так надо для его задачи.

У меня это задано с помощью функции isFinalizeAllowed, не вижу причин почему это надо делать именно разными типами.

Чтобы компилятор видел, опять же.

Описать 10 типов сложнее, чем 1.

Какие 10 типов у меня указано выше? У меня выше ровно два типа: тип, описывающий возможные состояния, и тип заказа. На сдачу ещё можно функцию, вычисляющую тип billing address'а по состоянию, посчитать, хз, но это тоже O(1) от количества состояний.

Отмена может быть возможна из 6 разных состояний, с типами придется их все указывать в аргументе и обновлять при добавлении нового разрешенного для отмены.

С чего бы?

CanCancel :: State → Bool
CanCancel Finalized = True
CanCancel Building = True
CanCancel _ = False

cancel :: CanCancel st ~ True ⇒ Order st → Order Cancelled

Добавляете просто ещё один кейс в CanCancel.

А для одного из них есть дополнительный критерий отмены, который надо проверять логикой, и часть требований будет в типах, а часть в алгоритме.

Нет, зачем?

То есть сложность не уменьшается, а переходит из одной формы в другую.

И это — непосредственная сложность задачи, потому что она непосредственно связана со спекой. Вы можете просто взять и показать все эти типы бизнес-аналитику, и с небольшой тренировкой на полчаса он сможет их прочитать (и только их) и перепроверить, что логика вроде как имеет смысл.

Это практически то, о чём пишут всякие дяди бобы, только сделанное по-нормальному и проверяемое компилятором.

Я все-таки имел в виду без этого. Вы не убираете необходимость лезть в детали, а вытаскиваете их наружу в виде типа, уменьшая уровень абстракции.

Абстракция — не самоцель, не все абстракции одинаково полезны. Я не хочу абстрагировать непосредственную бизнес-задачу, чтобы она была где-то внутри, а снаружи торчали абстрактные фабрики декораторов спринг-бинов.

Они подбираются под бизнес-требования, но выражают формат данных, которые лежат в переменной.

Формат данных и его семантику. Целое число. Положительное число. Делитель вон того числа.

А зачем нужно-то, чтобы проверял именно компилятор? Можно же использовать другой инструмент. Он не лучше или хуже, а просто другой.

И какие инструменты дадут мне по рукам за написание ерунды и помогут писать правильный код прямо во время написания кода, до того, как я не то что код в продакшен выкатил, а даже файл сохранил?

Ну так и с ФП можно написать только 1 тип, а не как у вас.

Ну да. И с ООП можно всё написать в одном классе, прямо в public static void Main.

Только в ООП тяжело требовать от компилятора проверок, а в типизированном ФП — легко.

Вот я и говорю, если мы пишем новый код, например добавляем метод shipOrder, то никакой существующий код нам не помешает случайно написать shipOrder :: Order Paid → Order Finalized.

Помешает, если алгебра работы с ордерами прописана в отдельном модуле (который менять не надо, и который не экспортирует кишки ордеров), а конкретные сценарии работы с ними вы описываете снаружи.

наследуем его, переопределяем, если надо для него enter() и exit() - то что будет обрабатываться при переходе в стейт и покидании его.

Подождите, я не понимаю. Как вы разделяете, что идёт в enter «следующего» состояния, а что — в exit «предыдущего»?

Вот у меня есть, опять же, пусть простой случай — заказ в состоянии Finalized. При переходе в состояние Paid надо проверить, что платёжные реквизиты норм, и выслать письмо покупателю. Что тут куда идёт?

дополняем fsm методом gotoSaving()

Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?

реализуем логику перехода в новый стейт, там, где это надо по задаче

«А теперь рисуем сову»

Кто это вызывает? Это вызывается снаружи fsm? Какова его роль тогда? В чём разница между gotoSaving и Saving::enter?

Тяжело представить. Можете всё-таки показать пример? Я вот потрудился и набросал немного наиболее характерного кода.

Никак, так же как у вас. Сколько сделали типов, столько и будет. Зачем их ограничивать?

У меня количество состояний ограничено, новые добавить нельзя «снаружи», без модификации типа State. У вас — можно.

new с копированием полей. Потому так никто и не делает.

Тяжеловесный синтаксис мешает выражать мысли, не удивлён.

Так мы сами задаем осмысленные операции, а не компилятор.

Мы задаём (постулируем) атомарные операции. Осмысленность их композиции проверяет компилятор.

Да и даже осмысленность их задания компилятор проверить может — выявляемый косяк с размером фрейма композиции энкодеров я описывал рядом.

Обычно надо сделать новую функциональность, а не вызвать существующую, потому что существующая и так уже вызывается в существующем коде.

Новая функциональность может быть сделана в терминах «описать пайплайн для заказов со скидками и промо-акциями». И тут type-driven development вместе с type-driven-рефакторингом очень помогают.

В том и дело, что finalizeOrder совсем необязательно должна ставить какой-то статус. Что надо делать, описано в бизнес требованиях.

В рамах данной модельной задачи финализированный ордер — это отдельная сущность, потому что к нему применим строго определённый (и отличный от других состояний ордеров) набор операций.

Всё в типе не опишешь.

Опишешь :]

Но даже если бы это было не так,

Там много всего, и получения нужной информации все равно надо смотреть реализацию.

— стремление снизить необходимость лезть в детали реализации ИМХО осмысленно и вполне естественно.

Точно так же, как знание того, что функция принимает int, не отменяет необходимости понимать, что она потом с ним делает, но при этом всё равно оказывается очень полезным.

Типы переменных это техническая информация, а статусы заказа это бизнес-требования.

Типы переменных тоже выражают бизнес-требования. Телефон — скорее строка (а не число) потому, что «phone 0123» ≠ «phone 123». Количество компьютеров на складе — целое число потому, что у вас не может быть пол-компьютера. Деньги в трейдинговой системе выражаются как fixed point с N разрядами после точки потому, что таковы бизнес-требования.

Можно пытаться изложить бизнес-требования средствами для технической информации, но мы возвращаемся к вопросу "зачем?".

Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»

Так в императивном коде это тоже будет отдельная новая функция, сразу бросающаяся в глаза.

Не обязательно. Это вполне может быть скрыто где-то в деталях реализации в ООП-коде. ООП-код вида

oldState = this.state;
this.state = Finalized;
this.doSomethingExpectingFinalizedState();
this.state = oldState;

просто потому, что так проще, я видел.

На хаскеле можно (и очень приятно) делать DSL'и в том числе для глубокого эмбеддеда: например, ivory.

Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.

if (!this.isFinalizeAllowed(order))

Рантайм-проверка же. Это совсем не то же самое.

Можно сделать классы OrderBuilding extends Order, OrderFinalized extends Order, но обычно так никто не делает.

Как тогда ограничить количество наследников-«состояний»?

И, кстати, из любопытства, как будет выглядеть создание OrderFinalized из OrderBuilding в вашем любимом языке? У них одинаковые поля, и в хаскеле я с RecordWildcards могу сделать finalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).

Да, значение поля в типах не проверяется. А надо? А зачем?

Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.

И для того, чтобы у меня были явные подсказки, что я сейчас могу сделать дальше с заказом. В чуть более сложных стейтмашинах/бизнес-логике это помогает сильнее, чем может показаться на первый взгляд: часто вообще тупо из типов становится понятно, что делать, и подход «подумал над предметной областью ⇒ записал типы ⇒ выключил мозг и пишешь реализации» приводит к успеху.

В коде явно указано, что finalizeOrder поставит только статус Finalized и никакой другой.

Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.

Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».

И с типами точно так же, берем и делаем метод setFinalized :: Order Shipped → Order Finalized

Это отдельная функция, сразу бросающаяся в глаза. Практика показывает, что так почему-то косячить сложнее, чем когда оно скрыто где-то в глубинах реализации.

Вот я бы хотел, чтобы человек показал этот вариант хотя бы самому себе.

Пишем что-то про заказы.

У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.

Заказ создаётся в состоянии «составляется». В этом же состоянии (и только в нём) в него можно добавлять товары. Из этого же состояния его можно финализировать. После того, как заказ финализирован, его можно оплатить (и вы либо получите ошибку оплаты, либо заказ перейдёт в состояние «оплачен»). После того, как заказ оплачен, его можно отправить покупателю. Никаких других переходов в системе нет.

Но да, очевидно можно, если приведённый код делает что-либо осмысленное

Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде

createOrder addr = Order addr [] ()

addItems items Order{..} = Order{.., goods = goods <> items }

finalizeOrder Order{..} = Order{..}

Так что, можно теперь ООП-вариант?

В неискусственных задачах лучше выходит. Вот, например, кусок NFA-интерпретатора с иммутабельными векторами:

match :: StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = go initState 0 mempty
  where
  go q i stack
    | q == finState = SuccessAt i
    | otherwise = case q `getTrans` transitions of
             TEps q' -> go q' i stack
             TBranch q1 q2 -> go q1 i (stack `V.snoc` (q2, i))
             TCh ch q'
               | bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
               | Just (stack'', (q'', i'')) <- V.unsnoc stack -> go q'' i'' stack''
               | otherwise -> Failure

Вот он же с мутабельными линейными:

match :: forall q. StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = L.unur L.$ VL.empty L.$ go initState 0
  where
  go :: q -> Int -> VL.Vector (q, Int) %1-> L.Ur (MatchResult Int)
  go q i stack
    | q == finState = stack `L.lseq` L.Ur (SuccessAt i)
    | otherwise = case q `getTrans` transitions of
                    TEps q' -> go q' i stack
                    TBranch q1 q2 -> go q1 i L.$ (q2, i) `VL.push` stack
                    TCh ch q'
                      | bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
                      | otherwise -> case VL.pop stack of
                                      (L.Ur top, stack'')
                                        | (Just (q'', i'')) <- top -> go q'' i'' stack''
                                        | otherwise -> stack'' `L.lseq` L.Ur Failure

Information

Rating
1,748-th
Registered
Activity