Реализуется указателями на thunk'и. x = 0:y означает, упрощая, что в памяти создаётся cons-cell (которая сама по себе держит два указателя), где первый (head) указатель указывает на 0, а второй (tail) — на y. Аналогично y — это cons-cell, где первый указатель указывает на 1, а второй — на x. Так как : — ленивый конструктор, то вычислять его аргументы не нужно, поэтому всё работает.
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, и сводится не только к двусвязным спискам, но и, например, к деревьям, где у каждого узла есть и (указатель на) родитель, и дети.
Перекат в Европу хорош, если вы ниже примерно среднего (и тогда вам все эти социальные гарантии важны и полезны), и плох (на фоне переката в США, скажем), если вы выше среднего (когда все эти социальные гарантии только мешают и ограничивают). Ну, просто потому, что в последних, например, доступны кратно более высокие зарплаты, с которых вы ещё более кратно больше получите на руки, и вопроса «а сколько у меня отпуска в год» не стоит вообще потому, что можно позволить себе регулярно год-два не работать вообще.
Ну и пусть. Если программист пишет новый класс 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 разрядами после точки потому, что таковы бизнес-требования.
Можно пытаться изложить бизнес-требования средствами для технической информации, но мы возвращаемся к вопросу "зачем?".
Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»
Так в императивном коде это тоже будет отдельная новая функция, сразу бросающаяся в глаза.
Не обязательно. Это вполне может быть скрыто где-то в деталях реализации в ООП-коде. ООП-код вида
Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.
Можно сделать классы OrderBuilding extends Order, OrderFinalized extends Order, но обычно так никто не делает.
Как тогда ограничить количество наследников-«состояний»?
И, кстати, из любопытства, как будет выглядеть создание OrderFinalized из OrderBuilding в вашем любимом языке? У них одинаковые поля, и в хаскеле я с RecordWildcards могу сделать finalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).
Да, значение поля в типах не проверяется. А надо? А зачем?
Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.
И для того, чтобы у меня были явные подсказки, что я сейчас могу сделать дальше с заказом. В чуть более сложных стейтмашинах/бизнес-логике это помогает сильнее, чем может показаться на первый взгляд: часто вообще тупо из типов становится понятно, что делать, и подход «подумал над предметной областью ⇒ записал типы ⇒ выключил мозг и пишешь реализации» приводит к успеху.
В коде явно указано, что finalizeOrder поставит только статус Finalized и никакой другой.
Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.
Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».
И с типами точно так же, берем и делаем метод setFinalized :: Order Shipped → Order Finalized
Это отдельная функция, сразу бросающаяся в глаза. Практика показывает, что так почему-то косячить сложнее, чем когда оно скрыто где-то в глубинах реализации.
У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.
Заказ создаётся в состоянии «составляется». В этом же состоянии (и только в нём) в него можно добавлять товары. Из этого же состояния его можно финализировать. После того, как заказ финализирован, его можно оплатить (и вы либо получите ошибку оплаты, либо заказ перейдёт в состояние «оплачен»). После того, как заказ оплачен, его можно отправить покупателю. Никаких других переходов в системе нет.
Но да, очевидно можно, если приведённый код делает что-либо осмысленное
Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде
В неискусственных задачах лучше выходит. Вот, например, кусок 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
Реализуется указателями на thunk'и.
x = 0:yозначает, упрощая, что в памяти создаётсяcons-cell (которая сама по себе держит два указателя), где первый (head) указатель указывает на 0, а второй (tail) — наy. Аналогичноy— этоcons-cell, где первый указатель указывает на 1, а второй — наx. Так как:— ленивый конструктор, то вычислять его аргументы не нужно, поэтому всё работает.Норм вариант, выглядит разумно.
Для того, чтобы иметь такой же в абсолютных величинах достаток, как 100 лет назад, вам сегодня нужно работать сильно меньше восьми часов в сутки.
Тогда пишите на JS.
А, вы имели в виду рантайм-поведение? Тогда почему (компилтайм-)блокирование, (компилтайм-)локи для вас влияют на что-то в рантайме?
Я уж не говорю о том, что более ограниченная семантика языка помогает компилятору оптимизировать код.
Жаль, что матчасть никто выучить не способен. Вон, из недавнего:
Всё как всегда. Всё как мы любим в сишке. И это всё в
за год на менее чем 300к строк кода — по промышленным меркам милипусечный проект.
Какое параллельное программирование? Массивы и их индексацию бы освоить.
Это (
Middle) будет именно ячейкой с двумя указателями.Другое дело, что он будет очень жёстко иммутабельный: у вас не получится его поменять (даже добавить узел в начало или в конец) без пересоздания вообще всей структуры. По крайней мере, именно в таком виде (я не могу сходу придумать способ это обойти иммутабельным образом, но и не могу строго доказать, что его не существует).
Лень позволяет использовать объект до его полного определения. В хаскель-тусовке это называется tying the knot, и сводится не только к двусвязным спискам, но и, например, к деревьям, где у каждого узла есть и (указатель на) родитель, и дети.
Перекат в Европу хорош, если вы ниже примерно среднего (и тогда вам все эти социальные гарантии важны и полезны), и плох (на фоне переката в США, скажем), если вы выше среднего (когда все эти социальные гарантии только мешают и ограничивают). Ну, просто потому, что в последних, например, доступны кратно более высокие зарплаты, с которых вы ещё более кратно больше получите на руки, и вопроса «а сколько у меня отпуска в год» не стоит вообще потому, что можно позволить себе регулярно год-два не работать вообще.
И Merkel-Lego нет, эх.
Почти все imagebin-сервисы (особенно из всяких списков вроде «клоны pomf.cat») закрываются ровно по этим причинам.
Если программист делает ошибки, значит ему так надо для его задачи.
Чтобы компилятор видел, опять же.
Какие 10 типов у меня указано выше? У меня выше ровно два типа: тип, описывающий возможные состояния, и тип заказа. На сдачу ещё можно функцию, вычисляющую тип billing address'а по состоянию, посчитать, хз, но это тоже O(1) от количества состояний.
С чего бы?
Добавляете просто ещё один кейс в
CanCancel.Нет, зачем?
И это — непосредственная сложность задачи, потому что она непосредственно связана со спекой. Вы можете просто взять и показать все эти типы бизнес-аналитику, и с небольшой тренировкой на полчаса он сможет их прочитать (и только их) и перепроверить, что логика вроде как имеет смысл.
Это практически то, о чём пишут всякие дяди бобы, только сделанное по-нормальному и проверяемое компилятором.
Абстракция — не самоцель, не все абстракции одинаково полезны. Я не хочу абстрагировать непосредственную бизнес-задачу, чтобы она была где-то внутри, а снаружи торчали абстрактные фабрики декораторов спринг-бинов.
Формат данных и его семантику. Целое число. Положительное число. Делитель вон того числа.
И какие инструменты дадут мне по рукам за написание ерунды и помогут писать правильный код прямо во время написания кода, до того, как я не то что код в продакшен выкатил, а даже файл сохранил?
Ну да. И с ООП можно всё написать в одном классе, прямо в public static void Main.
Только в ООП тяжело требовать от компилятора проверок, а в типизированном ФП — легко.
Помешает, если алгебра работы с ордерами прописана в отдельном модуле (который менять не надо, и который не экспортирует кишки ордеров), а конкретные сценарии работы с ними вы описываете снаружи.
Подождите, я не понимаю. Как вы разделяете, что идёт в
enter«следующего» состояния, а что — вexit«предыдущего»?Вот у меня есть, опять же, пусть простой случай — заказ в состоянии
Finalized. При переходе в состояниеPaidнадо проверить, что платёжные реквизиты норм, и выслать письмо покупателю. Что тут куда идёт?Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?
«А теперь рисуем сову»
Кто это вызывает? Это вызывается снаружи fsm? Какова его роль тогда? В чём разница между
gotoSavingиSaving::enter?Тяжело представить. Можете всё-таки показать пример? Я вот потрудился и набросал немного наиболее характерного кода.
У меня количество состояний ограничено, новые добавить нельзя «снаружи», без модификации типа State. У вас — можно.
Тяжеловесный синтаксис мешает выражать мысли, не удивлён.
Мы задаём (постулируем) атомарные операции. Осмысленность их композиции проверяет компилятор.
Да и даже осмысленность их задания компилятор проверить может — выявляемый косяк с размером фрейма композиции энкодеров я описывал рядом.
Новая функциональность может быть сделана в терминах «описать пайплайн для заказов со скидками и промо-акциями». И тут type-driven development вместе с type-driven-рефакторингом очень помогают.
В рамах данной модельной задачи финализированный ордер — это отдельная сущность, потому что к нему применим строго определённый (и отличный от других состояний ордеров) набор операций.
Опишешь :]
Но даже если бы это было не так,
— стремление снизить необходимость лезть в детали реализации ИМХО осмысленно и вполне естественно.
Точно так же, как знание того, что функция принимает
int, не отменяет необходимости понимать, что она потом с ним делает, но при этом всё равно оказывается очень полезным.Типы переменных тоже выражают бизнес-требования. Телефон — скорее строка (а не число) потому, что «phone 0123» ≠ «phone 123». Количество компьютеров на складе — целое число потому, что у вас не может быть пол-компьютера. Деньги в трейдинговой системе выражаются как fixed point с N разрядами после точки потому, что таковы бизнес-требования.
Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»
Не обязательно. Это вполне может быть скрыто где-то в деталях реализации в ООП-коде. ООП-код вида
просто потому, что так проще, я видел.
На хаскеле можно (и очень приятно) делать DSL'и в том числе для глубокого эмбеддеда: например, ivory.
Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.
Рантайм-проверка же. Это совсем не то же самое.
Как тогда ограничить количество наследников-«состояний»?
И, кстати, из любопытства, как будет выглядеть создание
OrderFinalizedизOrderBuildingв вашем любимом языке? У них одинаковые поля, и в хаскеле я сRecordWildcardsмогу сделатьfinalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.
И для того, чтобы у меня были явные подсказки, что я сейчас могу сделать дальше с заказом. В чуть более сложных стейтмашинах/бизнес-логике это помогает сильнее, чем может показаться на первый взгляд: часто вообще тупо из типов становится понятно, что делать, и подход «подумал над предметной областью ⇒ записал типы ⇒ выключил мозг и пишешь реализации» приводит к успеху.
Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.
Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».
Это отдельная функция, сразу бросающаяся в глаза. Практика показывает, что так почему-то косячить сложнее, чем когда оно скрыто где-то в глубинах реализации.
Вот я бы хотел, чтобы человек показал этот вариант хотя бы самому себе.
Пишем что-то про заказы.
У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.
Заказ создаётся в состоянии «составляется». В этом же состоянии (и только в нём) в него можно добавлять товары. Из этого же состояния его можно финализировать. После того, как заказ финализирован, его можно оплатить (и вы либо получите ошибку оплаты, либо заказ перейдёт в состояние «оплачен»). После того, как заказ оплачен, его можно отправить покупателю. Никаких других переходов в системе нет.
Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде
Так что, можно теперь ООП-вариант?
В неискусственных задачах лучше выходит. Вот, например, кусок NFA-интерпретатора с иммутабельными векторами:
Вот он же с мутабельными линейными: