User avatar

я в некотором невменозе сегодня продолжал думать над /squadette/1624296, и вот что я бы сделал чтобы эксплицировать обработку "ошибок" в некоем гипотетическом языке: а) во-первых, практически все функции "возвращают" только union types, "чистых" функций крайне мало (например, "длина строки" или "поиск значения в структуре данных").

Comment

б) во-вторых, функции ничего не возвращают (as in, "вверх по стеку"), а передают дальше, continuation-passing style, с синтаксисом, который представляет собой нечто среднее между ruby blocks и pattern match (щас попробую набросать псевдо-синтаксис чтобы самому понятнее было).

 ‎· псы в рапиде
Comment

Pattern match фактически обеспечивает то, что ошибочные состояния кодируются как бы не в виде значений, а виде различных code paths, что конечно же сильно надежнее.

 ‎· псы в рапиде
Comment

Различных паттернов в разных union types сразу может быть сколько угодно, то есть типичный парето-обусловленный кейс "результат или код ошибки" — это тривиальный частный случай. При этом мы вообще меньшем говорим об "ошибках", а больше говорим об альтернативах.

 ‎· псы в рапиде
Comment

Монады Maybe и Either являются не-первичными объектами — ты можешь при желании реализовать эти монады через вышеописанный базовый синтаксис. В качестве прикола — оператор if не является базовым синтаксисом, а реализуется как макрос через pattern match по union-type True | False.

 ‎· псы в рапиде
Comment

в) естественно, вся эта ботва статически типизированная, иначе никак.

 ‎· псы в рапиде
Comment

г) видимо, чтобы обработать частые случаи и дать дополнительный хинт валидатору, можно подумать о том, чтобы маркировать определенные альтернативы как "happy path", а другие как "error path". при этом эти маркеры полностью независимы, любые альтернативы могут быть помечены как один или другой или не помечены.

 ‎· псы в рапиде
Comment

Бессмысленный драфт синтаксиса: https://gist.github.com/squadette/cda3c1e40d2c3c7cdcf3d7e9c3da015b Бессмысленный он потому, что опять разговаривает про ошибки.

 ‎· псы в рапиде
Comment

Алексей, а чот ни тут, ни там не вижу ничего про линейные типы — почему? Они как раз же для этого.

 ‎· адский хардлайн в засаде
Comment

д) Предположим, нам дали массив арифметических выражений, и мы хотим их все вычислить и вернуть их значения или ошибку вычисления (деление на ноль etc). Видимо, мы можем map этот массив в массив "результатов", но сделать с ними мы не можем ничего, кроме как прогнав их через reduce, который паттерн-матчит ("выполнит") эти выражения, и в каждом из двух путей (Результат a | ОшибкаВычисления msg) например вернет пару из массива успешных значений и массива ошибок.

 ‎· псы в рапиде
Comment

@larhat: у меня было ощущение, которое подтверждается википедией, что линейные типы это про передачу контроля над ресурсом. Я думаю скорее про что-то, где "доступ к файлу или к локу" — это частный случай. Я не говорю вообще ничего нового с точки зрения реализации, я только думаю о том, как заставить человека всегда смотреть на слона в каждой строчке. Вообще наверное линейные типы можно применить к чему угодно, но тут я лох, потому что никогда ими не пользовался и понимаю на уровне черного ящика (вопрос: Rust ownership — это близко?)

 ‎· псы в рапиде
Comment

Так вот, д2) короче, "значения" бывают чистые и нечистые (слова довольно плохие). нечистые значения можно только паттерн-матчить. чистые значения это либо strlen(), либо их можно явно паттерн-матчить из нечистых, непонятно только зачем. Собственно, монада Either, реализованная через паттерн-матч — это формально чистое значение, но при этом ты теряешь весь смысл этого подхода.

 ‎· псы в рапиде
Comment

Update: Если я правильно прочитал http://wiki.c2.com/?LinearTypes, то линейные типы не совсем про это, они ортогональны тому про что я говорю. Они позволят тебе убедиться, что у тебя файл закрывается всегда, когда он был открыт, но не помогут тебе, если у тебя в файле битый сериализованный объект.

 ‎· псы в рапиде
Comment

Я правильно понимаю, что в этом концепте не предусмотрено short-circuit? То есть в каждом вложенном вызове придётся обрабатывать оба кейса, примерно как в си без эксепшенов (но и без goto)? Не приведет ли это к некоторому многословию? Даже на твоём тривиальном примере видно.

 ‎· sober, steady, good provider
Comment

@markizko: да. смотри, есть охуенная статья: https://web.stanford.edu/~engler/explode-osdi06.pdf. там приводится такой пример: например, для создания файла нам надо выделить inode, найти свободное место в каталоге, если места нет, то выделить память для нового extent, потом записать его на диск, потом найти свободное место в новом экстенте, записать туда inode. Итого четыре шага. на каждом шаге у нас может не хватить памяти или возникнуть I/O error при записи на диск. Соответственно ~2^4 возможных вариантов. EXPLODE симулирует ошибки всех вызовов и тем самым триггерит полный комбинаторный просмотр вариантов выполнения, и выясняет, правильно ли освобождаются ресурсы или отменяются транзакции при ошибках в любом из возможных вариантов. Они нашли дикое количество ошибок таким способом. Мой же тезис состоит в том, что возможно, что авторы такого кода должны сами видеть полную сложность этого кода. Поэтому да, он "многословен", но только потому, что такова природа задачи.

 ‎· псы в рапиде 1
Comment

Вообще конечно явно напрашивается аналог обработки исключений на верхнем уровне, это правда. Это будет выглядеть так, что сигнатура процедуры "вычислить массив арифметических выражений и записать его в файл", будет очень развесистой (ошибка открытия | ошибка вычисления | ошибка записи | ошибка выделения памяти | ошибка закрытия файла). Предположим, что нам надо прочитать выражения из одного файла и записать результаты в другой. У нас в сигнатуре появляется альтернатива "ошибка открытия файла". Внимание вопрос — какого файла? У обоих вызовов syscall_open() будет одна и та же сигнатура, и ошибки будут маскированы. В принципе, правда, можно компилятором проверять перекрытие сигнатур и требовать обработать одну, если она мешает надежно поймать другую. Ответил ли я на вопрос?

 ‎· псы в рапиде
Comment

^ вот да, с исключениями все хорошо, если у тебя последовательность выполнения "линейная", то есть из набора операций может выполниться любые первые N (ну или все в случае happy path). А если у тебя последовательность выполнения графовая, то становится сильно сложнее. От этого же собственно и постоянно FSM'ки выползают из каждого угла, когда начинаешь об этом думать.

 ‎· псы в рапиде
Comment

В чём главные преимущества предлагаемой схемы по сравнению со старыми недобрыми java checked exceptions?

 ‎· jsn
Comment

@jsn: "They were eliminated in C# for example, and most languages don't have them", гггг. Первый раз кстати про них слышу, видимо я перестал писать на яве до того, как они появились. Да, это конечно форсирование обработки исключений, но при этом еще "исключения" это частный случай "альтернативы" или как там это назвать.

 ‎· псы в рапиде 2
Comment

@squadette: насколько я понимаю, они появились вместе с явой, в лихие 90-е. в длинном списке причин ненавидеть яву они находятся где-то ближе к началу (в первой сотне)

 ‎· jsn
Comment

re: linear types. Ну да, я их вспомнил больше к предыдущему посту: "обнуленный ключ переустанавливается при повторе" и про то что обнулять нужно явно. Можно представить api типа create-key : () -> Key; install-key : Key -> InstalledKey; reset : InstalledKey -> () — тогда linear type system будет гарантировать, что это всё вызвано, и что InstalledKey не остался валятся и будет явно обнулён (неявно это можно делать с деструкторами и афинными типами, которые не позволят сохранить Key где-то ещё). --- Но и к общей проблеме ошибок linear types можно применить: так как тайпчекер гарантирует, что значение дожно быть использовано ровно один раз, можно представить тип RecoverableError<Op<Result>> и retry, которые может с этим работать, а также FatalError, с которым можно сделать только halt : FatalError -> (). UPD: понятно, что pattern matching должен быть exhaustive, но я так думаю, что linear type system должна это по умолчанию уметь.

 ‎· адский хардлайн в засаде
Comment

^ Если более практически думать, то уже сейчас в окамле например, можно включить ворнинги на non-exhaustive match и дискард не unit значения. Соот-но с -Werr и апи, где функции возвращают Result/Either это может помочь. Другое дело, что это бывает утомительно и есть всякие ignore : 'a -> (), которые могут свести на нет :(

 ‎· адский хардлайн в засаде 1
Comment

Я сейчас использую 1) трюк с session types - это гарантирует валидную смену состояний в compile time 2) error chaining - ошибки накапливаются (Either, с правой стороной в виде стэка ошибок) это гарантирует последовательную обработку bad path 3) exhaustive matching. Этих трёх хреновин достаточно для валидной обработки почти всего. Все эти хреновины есть в Rust, и возможны в хаскел/окамл. Одна из причин почему я так обрадован Rust.

 ‎· for hysterical raisins 3
Comment

(Замечу, что "функция, возвращающая union(X|Y|...|Z)" может рассматриваться как возвращающая рёбра графа state machine, по которым в результате можно перейти, а функция соотв. живёт в узле.)

 ‎· 9000
Comment

В рамках маньячества попользовался на практике паттерном "всё возвращает Either" (на питоне; функции почти все impure). Т.е. типичный код выглядит примерно так (грубый пример): Right(filename).and_then(open, 'w').and_then(add_to_tuple, (http.get, "http://some.url")).and_then(lambda output_file, response: map(output_file.write, response.text)). Заметил, что прямолинейное применение ведёт к тому, что наружу всплывают ошибки самых разных сортов, их хочется классифицировать, etc, и всё это начинает напоминать исключения. С другой стороны, если запихать обработчики возможных ошибок как можно ближе к их потенциальным источникам (обернуть, где надо, в retry, etc), то наружу не просачивается ошибок, на которые надо особо реагировать (звать тот же retry), а просачивается только финальное "не шмогла", если что, или состояние успеха. Второе, что пришлось сделать — это список "контекста" (аналогично error chaining ^^ у @ayoshi, только накапливаются не собственно ошибки, а аналог stacktrace с ключевыми данными). Соотв. Left, если прилетает, умеет показать, откуда он прилетел, но это пока требует некоторых танцев, которые я надеюсь автоматизировать, как разберусь в эргономике. (Надо допилить и запушить на github наконец.)

 ‎· 9000 2

1 2 3 4 5 6 7 8 9 10