Здравствуйте, Pavel Dvorkin, Вы писали:
PD>А я не писал, что проверяемые. Я просто сказал, что в Delphi были. А в TurboPascal нет вроде.
Я же специально включил цитату, на которую вы отвечали:
S>>Это очень странный ход рассуждений. Во-первых, с тем же успехом можно сказать, что они были под влиянием Pascal — ведь там тоже нет checked exceptions.
PD>...Вот в Delphi точно были.
Re[9]: Result objects - все-таки победили Exceptions?
Здравствуйте, Pavel Dvorkin, Вы писали: PD>Так, давай по порядку.
PD>До того, как мы добавили Cloud FS, все выглядело так
PD>1. Верхний уровень. Получает от среднего (треш и угар) HTTPException, решает что в этом случае делать. PD>2. Средний уровень. Работает с HTTP, черт знает как, но работает. Иногда выбрасывает HttpException. Обращается к нижнему уровню для получения каких-то файлов. PD>3. Нижний уровень. Работает с ФС. Пока что у нас там или NTFS, или какая-то линуксовская ФС.
Нет. Никакого HTTPException в природе не было, т.к. средний уровень рассчитывает на совершенно определённую сигнатуру FS. В этой сигнатуре нет и не может быть никаких HTTPException.
PD>Теперь вместо NTFS у нас некая CloudFS.
PD>Ну если ты ФС, то будь добра вести себя как ФС. Собственно, даже не будь добра, а никуда не денешься. Потому что средний слой обращается к тебе как к ФС и не ждет от тебя никакого HTTP, равно как и FTP и т.д.
Ну так в этом и проблема, что он ничего не ждёт. А клауд — это клауд. И притворяться, что он такой же, как локальная FS — деградировать качество сервиса.
PD>Но не получилось. Иногда этот нижний слой выбрасывает XyzException, связанный с HTTP. Средний слой его не ждет и вообще-то обрабатывать не может. Поэтому они идет выше, в верхний слой, а это значит, что средний слой вообще-то потерпел неудачу — он вызвал нижний, а там необрабатываемая проблема. Значит, средний слой свое дело вообще не выполнил. Так что все верно, она попадает в верхний как неожиданное исключение. Просил средний слой какой-то файл ему дать, а в ответ — XyzException.
PD>Короче, верхний слой должен ловить HttpException от среднего, именно там и идет работа с HTTP. А не от нижнего.
В среднем слое нет никакой "работы с HTTP".
PD>Ну в Pascal как он был вначале их нет вообще. Вроде как и в TurboPascal их не было, не помню точно. Вот в Delphi точно были.
Отож. Тем более, что один из основных авторов языка C# и есть создатель Object Pascal (ЯП, применяемого в RAD IDE "Delph").
PD>Вполне можно. Там ее не было, и тут не будет. Унаследовали отсутствие А то, что она есть в параллельном проекте (Java) — нам не указ.
PD>checked exceptions и есть предвиденные неприятности, и их обрабатывают явно.
PD>Если NPE может произойти, то оно произойдет. Если NPE произойти не может, оно все равно произойдет.
PD>Основная суть проблемы вот в чем. Ты доказываешь, что применение checked не панацея и приводишь примеры, когда они не очень успешно могут быть применимы. Я не спорю, не всегда все хорошо получится. Но зачем же, если нельзя обеспечить 100% успеха, заявлять, что это вообще никуда не годится?
Затем, что самый нормальный способ использовать checked exceptions — это не использовать checked exceptions. Иначе оказывается, что они создают больше проблем, чем решают.
PD>Вот тебе простой пример.
PD>void read(char * filename){ PD>FILE* f = fopen(filename, "rb"); PD>// чтение PD>fclose(f); PD>} PD>написал и запустил . Работает как часы, пока файл есть. А если его вдруг не окажется, будет хоть и не исключение (это же С), но плохо будет. А я забыл проверку поставить.
Ну, я и говорю — пока у нас иерархия глубиной 1, всё в порядке. Как только появляются более глубокие иерархии — например, в qsort(), то мгновенно начинаются проблемы на ровном месте.
PD>void read(String filename) { PD>FileInputStream fis = new FileInputStream(new File(filename); PD>// чтение PD>close(fis); PD>}
PD>Не компилируется. На FileInputStream диагностика unhandled exception FileNotFoundException. На close — IOException. PD>Иди-ка, автор, и подумай, что ты делаешь. А если файла нет ? А если закрытие не прошло почему-то ? Изволь подумать, что делать и код написать.
PD>Ну и что тут плохого ? Бог с ними, с верхними и средними уровнями и колбеками, но тут-то что плохого ?
То, что когда я пытаюсь написать код с sort, компаратор не имеет права ничего выбрасывать. Это означает, что для отношений частичного порядка этот метод непригоден. В отсутствие checked exceptions я могу выбросить из компаратора исключение, чтобы показать несравнимость объектов — и пусть caller метода Arrays.sort разбирается, что с этим делать.
Или там когда я пытаюсь найти в Stream<String> строку, которая эквивалентна числу 42 (в произвольном представлении), я не могу использовать очевидный Integer::parseInt. И заменить его ничем не могу, потому что нет никакой возможности внутри mapToInt вернуть "я не могу отобразить входящее значение ни в какой int".
Ну, кроме выброса RuntimeException / Error.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: Result objects - все-таки победили Exceptions?
Здравствуйте, korvin_, Вы писали: _>Сейчас это нигде не указано, кроме, разве что документации. Но вы не про документацию писали, а систему типов.
С моей точки зрения это примерно одно и то же. Вопрос именно в том, какой тип имеет результат операции new.
_>Удачи убедить хоть кого-нибудь писать на таком языке.
Возможно, вы правы.
_>И как это должно выглядеть?
Давайте повернём вопрос в другую сторону: а как это сделано в Haskell?
Я бегло почитал — ребята пишут, что исключения не требуют специальной языковой поддержки, т.к. для них достаточно монад. Ну, ок.
А что произойдёт, если хаскелл не смог сконструировать Add из Expr и Expr из-за OutOfMemory или StackOverflow?
_>-- ну и print, конечно, должен иметь сигнатуру вида _>-- print :: Show a => (a | OOM) -> (IO () | OOM) _>[/haskell]
_>То есть фактически OOM придётся писать буквально везде. Добавим сюда также StackOverflow, DivisionByZero и чёрт знает что ещё.
Я не очень понимаю, почему нам придётся писать OOM буквально везде. В первом варианте вы не писали никаких сигнатур у Add Expr Expr, а добавление OOM почему-то его потребовало.
Почему нельзя было просто добавить OOM в определение Expr как одной из альтернатив?
_>Удачи убедить хоть кого-нибудь писать на таком языке, да и самому не свихнуться от такого.
На первый взгляд, Хаскелл как раз такой язык. В том смысле, что я не могу просто взять и сделать throw в произвольном месте, не меняя сигнатуру функции (как это сделано в C++ и C#)
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: Result objects - все-таки победили Exceptions?
S>>>Это очень странный ход рассуждений. Во-первых, с тем же успехом можно сказать, что они были под влиянием Pascal — ведь там тоже нет checked exceptions.
PD>>...Вот в Delphi точно были.
Ну значит, я не точно выразился. Прочитал, что исключения не могли быть заимствованы из Паскаля, и написал, что их там нет. А в Delphi есть. Какие — надо было уточнить
With best regards
Pavel Dvorkin
Re[12]: Result objects - все-таки победили Exceptions?
Здравствуйте, Sinclair, Вы писали:
_>>И как это должно выглядеть? S>Давайте повернём вопрос в другую сторону: а как это сделано в Haskell?
Что именно? OOM там не ловится никак. То же самое с SO — это внутренняя вещь рантайма.
В остальном — по-разному. Например, функция div имеет тип (Integral a => a -> a -> a) без указания какого либо исключения деления на 0, но исключение может возникнуть.
main :: IO ()
main = print $ 5 `div` 0
— никакого упоминания исключений.
Можно и перехватить:
import Control.Exception
test :: IO ()
test = print $ 5 `div` 0
main :: IO ()
main = catch test handler
where
handler :: SomeException -> IO ()
handler ex = putStrLn $ "Caught exception: " ++ show ex
S>Я бегло почитал — ребята пишут, что исключения не требуют специальной языковой поддержки, т.к. для них достаточно монад. Ну, ок.
Ну не совсем.
S>А что произойдёт, если хаскелл не смог сконструировать Add из Expr и Expr из-за OutOfMemory или StackOverflow?
Упадёт.
_>>-- ну и print, конечно, должен иметь сигнатуру вида _>>-- print :: Show a => (a | OOM) -> (IO () | OOM) _>>[/haskell]
_>>То есть фактически OOM придётся писать буквально везде. Добавим сюда также StackOverflow, DivisionByZero и чёрт знает что ещё. S>Я не очень понимаю, почему нам придётся писать OOM буквально везде. В первом варианте вы не писали никаких сигнатур у Add Expr Expr, а добавление OOM почему-то его потребовало. S>Почему нельзя было просто добавить OOM в определение Expr как одной из альтернатив?
Потому что OOM — это не часть Expr. Сигнатуры в примере с OOM вымышленные.
Что нам даст добавление OOM в качестве альтернативы?
Эти альтернативы -- просто конструкторы значений.
Всё равно, что в Java написать
var oom = new SomeInterface() {
...
};
Как это поможет?
expr0 = If (Mul ...) ... -- где здесь OOM?
_>>Удачи убедить хоть кого-нибудь писать на таком языке, да и самому не свихнуться от такого. S>На первый взгляд, Хаскелл как раз такой язык. В том смысле, что я не могу просто взять и сделать throw в произвольном месте, не меняя сигнатуру функции (как это сделано в C++ и C#)
IO так и делает: бросает исключения без указания в сигнатуре.
Вот теперь понял. Мы разные модели имели в виду. Я-то полагал, что средний слой работает сам с HTTP, но обращается к нижнему за какими-то данными из ФС. А ты имеешь в виду, что он с HTTP вовсе не работает.
Хорошо, тогда уточню модель.
Средний слой занимается машинной графикой. Там действительно может быть треш и угар, тем более что там еще и нативного кода полно.
Нижний слой дает ему картинки для этой графики, из ФС, а также другие файлы.
Пока нижний слой на базе NTFS, никакого HTTP вообще нет.
Заменяем NTFS на CloudFS и появляется HTTP в этой CloudFS и он может выбросить HTTPException.
Надеюсь, правильно понял теперь ?
В этом случае, действительно, при работе с CloudFS возможны HTTP-проблемы. Ну и что ? CloudFS выбрасывает HTTPException, и средний слой к нему не готов.
По-хорошему, CloudFS должна обрабатывать эти исключения сама. Она все же ФС, а значит, средний слой работает с ней как с ФС, а ФС не выбрасывает HTTPException. Какое, простите дело, слою машинной графики до того, как хранятся файлы ? Но допустим, все же выбросит. Поскольку средний слой не может его обработать в принципе, оно долетает до верхнего в виде RuntimeException, в которое оно завернуто. Верхний слой им займется.
Но это пока средний слой мы не можем править. А если все же можем, то это HttpException будет проходить по стеку методов среднего слоя и где-то обрабатываться.
Иными словами, сейчас средний слой должен обрабатывать FileNotFoundException как одно из исключений ФС. Обязан, иначе быть не может. Если же ФС изменяется и может выбросить еще какое-то CloudConnectionAbortedException, то ему нужно и это принять во внимание и прореагировать. Потому что это одна из ошибок ФС теперь.
Ну а пока он это не может делать, CloudConnectionAbortedException будет обрабатывать верхний слой. Он может лишь повторить операцию, если можно, или признать неудачу. Средний слой мог бы сделать что-то более разумное так же, как и для FileNotFoundException — если его средний слой поймает, это еще не причина кричать "караул", может быть, можно выполнить какое-то корректирующее действие.
S>Отож. Тем более, что один из основных авторов языка C# и есть создатель Object Pascal (ЯП, применяемого в RAD IDE "Delph").
Это да.
PD>>Вполне можно. Там ее не было, и тут не будет. Унаследовали отсутствие А то, что она есть в параллельном проекте (Java) — нам не указ. S>
А зря. Вот попал бы ты к индейцам какого-то племени , и оказался бы единственным бледнолицым и без перьев на голове в этой теплой компании. Отсутствие красного цвета кожи ты унаследовал от своих родителей, а отсутствие перьев — от европейской культуры. Отсутствие какого-то свойства тоже данные, если может быть и присутствие. Мы же не класс языка обсуждаем, а реальный мир.
PD>>Основная суть проблемы вот в чем. Ты доказываешь, что применение checked не панацея и приводишь примеры, когда они не очень успешно могут быть применимы. Я не спорю, не всегда все хорошо получится. Но зачем же, если нельзя обеспечить 100% успеха, заявлять, что это вообще никуда не годится? S>Затем, что самый нормальный способ использовать checked exceptions — это не использовать checked exceptions. Иначе оказывается, что они создают больше проблем, чем решают.
Ну как говорил один мой знакомый, "зафиксируем разногласия". В его устах это означало — к общему мнению прийти не удалось, но и обсуждать дальше не стоит.
PD>>Вот тебе простой пример.
PD>>void read(char * filename){ PD>>FILE* f = fopen(filename, "rb"); PD>>// чтение PD>>fclose(f); PD>>} PD>>написал и запустил . Работает как часы, пока файл есть. А если его вдруг не окажется, будет хоть и не исключение (это же С), но плохо будет. А я забыл проверку поставить. S>Ну, я и говорю — пока у нас иерархия глубиной 1, всё в порядке. Как только появляются более глубокие иерархии — например, в qsort(), то мгновенно начинаются проблемы на ровном месте.
PD>>void read(String filename) { PD>>FileInputStream fis = new FileInputStream(new File(filename); PD>>// чтение PD>>close(fis); PD>>}
PD>>Не компилируется. На FileInputStream диагностика unhandled exception FileNotFoundException. На close — IOException. PD>>Иди-ка, автор, и подумай, что ты делаешь. А если файла нет ? А если закрытие не прошло почему-то ? Изволь подумать, что делать и код написать.
PD>>Ну и что тут плохого ? Бог с ними, с верхними и средними уровнями и колбеками, но тут-то что плохого ? S>То, что когда я пытаюсь написать код с sort, компаратор не имеет права ничего выбрасывать. Это означает, что для отношений частичного порядка этот метод непригоден. В отсутствие checked exceptions я могу выбросить из компаратора исключение, чтобы показать несравнимость объектов — и пусть caller метода Arrays.sort разбирается, что с этим делать. S>Или там когда я пытаюсь найти в Stream<String> строку, которая эквивалентна числу 42 (в произвольном представлении), я не могу использовать очевидный Integer::parseInt. И заменить его ничем не могу, потому что нет никакой возможности внутри mapToInt вернуть "я не могу отобразить входящее значение ни в какой int".
Ну непригоден где-то, пусть. Я же не утверждаю, что он каждой дырке затычка. Непригоден — выброси unchecked, они же не отменены. В моем примере почему непригоден-то ?
S>Ну, кроме выброса RuntimeException / Error.
Именно. Но зачем отрицать везде, в том числе там, где вполне пригоден и уменьшает шансы сделать ошибку ?
With best regards
Pavel Dvorkin
Re[4]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
_>>>Моду на коды ошибок вместо исключений ввели разработчики Rust КБ>>Result objects — это не коды ошибок. _>Уже писал в этой же ветке
, что отличия настолько исчезающе малы, что их в лупу не разглядишь.
Потому что HRESULT? Сишный опыт — нерелевантен.
КБ>>Когда вы уже поймете что ваш сишный опыт абсолютно уже не релевантен >А если возвращаться к теме ошибок/исключений
А зачем от нее было уходить?
>, то первый раз столкнувшись с трейтом std::Error в Rust, у меня, среди матюков, проскочила мысль что это было предопределено — миллениалы должны были придумать достойного соперника для HRESULT
Ну да. std::Error плох потому что HRESULT. Сишный опыт — нерелевантен.
Re[4]: Result objects - все-таки победили Exceptions?
Здравствуйте, Marty, Вы писали:
M>Здравствуйте, Константин Б., Вы писали:
_>>>Моду на коды ошибок вместо исключений ввели разработчики Rust
КБ>>Так облажаться в первом же предложении. Result objects — это не коды ошибок. КБ>>Когда вы уже поймете что ваш сишный опыт абсолютно уже не релевантен.
M>Как я понял, это код ошибки + полезный результат, упакованное вместе. Те же яйца, вид сбоку
Неправильно понял. Это не код ошибки, это объект ошибки. Т.е. как исключение только явно возвращаемое из функции.
Совершенно лишенное недостатков кодов ошибок.
Re[3]: Result objects - все-таки победили Exceptions?
Здравствуйте, Shmj, Вы писали:
S>А что не современные этого не позволяли? В чем разница? Что принципиально нового появилось?
Сишечка не позволяла. На какой-нибудь яве легко можно было сделать.
S>Коды возврата использовали еще деды. Основная претензия была — из можно было забыть проверить.
Проблема специфичная для сишечки. Только их там не забывают. Их там просто не проверяют. Потому что с обработкой ошибок сишечка становится еще в 10 раз более убогая.
Оттуда и пошел миф про "забывают".
Например ты не найдешь не одной книги/статьи/туториала по го где "обработка ошибок опущена для простоты понимания".
Здравствуйте, SkyDance, Вы писали:
AD>>Хорошо ли это? Я думаю да. Знак вопроса не создаёт излишнего визуального шума и даёт при этом полезную информацию тому, кто читает код. Тут могут возразить те сторонники исключений, кому нафиг не сдалась инфа о том, что функция может бросить исключение. Но мне эта инфа нужна и знак ? в этом сильно помогает.
SD>Дело в том, что любая функция в любой момент может бросить исключение. Не OutOfMemory, так OutOfStack. И обработать это исключение можно только многими уровнями выше. Поэтому для меня это лишь бессмысленный шум, своеобразный карго-культ.
А раскажите пожалуйста как вы обрабатываете OutOfMemory и OutOfStack?
Re[13]: Result objects - все-таки победили Exceptions?
Здравствуйте, korvin_, Вы писали: _>Что именно? OOM там не ловится никак. То же самое с SO — это внутренняя вещь рантайма.
Ну, то есть panic. Скажем так: в такой среде сложно писать софт, который должен работать 24*7.
_>В остальном — по-разному. Например, функция div имеет тип (Integral a => a -> a -> a) без указания какого либо исключения деления на 0, но исключение может возникнуть.
Хм. То есть кидать можно всё, что угодно и где угодно?
Я посмотрел вот сюда. Написано
The great thing about Haskell is that it is not necessary to hard-wire the exception handling into the language.
То есть исключений нет, но если хочется прикрутить их поддержку, то можно их реализовать самостоятельно.
Читаем дальше:
Haskell solves the problem a diplomatic way: Functions return error codes, but the handling of error codes does not uglify the calling code.
Там написана неправда? _>Ну не совсем.
S>>А что произойдёт, если хаскелл не смог сконструировать Add из Expr и Expr из-за OutOfMemory или StackOverflow? _>Упадёт.
Как-то грустненько.
S>>Почему нельзя было просто добавить OOM в определение Expr как одной из альтернатив?
_>Потому что OOM — это не часть Expr. Сигнатуры в примере с OOM вымышленные. _>Что нам даст добавление OOM в качестве альтернативы? _>Эти альтернативы -- просто конструкторы значений.
А выглядит как алгебраический тип.
_>Всё равно, что в Java написать
_>
_>var oom = new SomeInterface() {
_> ...
_>};
_>
_>Как это поможет?
Поможет не писать везде Expr | OOM, а встроить OOM в Expr. _>
_>expr0 = If (Mul ...) ... -- где здесь OOM?
_>
В рамках Haskell, наверное, нигде. Язык не готов к работе в условиях дефицита памяти
_>IO так и делает: бросает исключения без указания в сигнатуре.
Ну, то есть в IO встроена возможность бросать всё что угодно? откуда в div появилась возможность "бросить" divisionByZero?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Result objects - все-таки победили Exceptions?
Здравствуйте, Alekzander, Вы писали:
A>Исключения нужны, чтобы прятать обработку и "спрямлять" поток выполнения.
Исключения нужны чтобы ошибки не обрабатывать. Просто игнорируем, в худшем случае процесс упадет. А по том по логам с продакшена уже расставляются кэчи где надо.
А если хочется ошибки правильно обработать заранее прямо при написании кода — исключения никуда не годятся.
Re[14]: Result objects - все-таки победили Exceptions?
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, korvin_, Вы писали: _>>Что именно? OOM там не ловится никак. То же самое с SO — это внутренняя вещь рантайма. S>Ну, то есть panic. Скажем так: в такой среде сложно писать софт, который должен работать 24*7.
Вот интересно, а что вы делаете в Java при получении OOM?
_>>В остальном — по-разному. Например, функция div имеет тип (Integral a => a -> a -> a) без указания какого либо исключения деления на 0, но исключение может возникнуть. S>Хм. То есть кидать можно всё, что угодно и где угодно? S>Я посмотрел вот сюда. Написано S>
S>The great thing about Haskell is that it is not necessary to hard-wire the exception handling into the language.
S>То есть исключений нет, но если хочется прикрутить их поддержку, то можно их реализовать самостоятельно. S>Читаем дальше: S>
S>Haskell solves the problem a diplomatic way: Functions return error codes, but the handling of error codes does not uglify the calling code.
S>Там написана неправда?
Там написано не про это. Насчёт does not uglify the calling code я бы поспорил.
_>>Ну не совсем.
S>>>А что произойдёт, если хаскелл не смог сконструировать Add из Expr и Expr из-за OutOfMemory или StackOverflow? _>>Упадёт. S>Как-то грустненько.
А что вы предлагаете делать при OOM?
S>>>Почему нельзя было просто добавить OOM в определение Expr как одной из альтернатив?
_>>Потому что OOM — это не часть Expr. Сигнатуры в примере с OOM вымышленные. _>>Что нам даст добавление OOM в качестве альтернативы? _>>Эти альтернативы -- просто конструкторы значений. S>А выглядит как алгебраический тип.
Это алгебраический тип, но при чём тут это?
_>>Всё равно, что в Java написать
_>>
_>>var oom = new SomeInterface() {
_>> ...
_>>};
_>>
_>>Как это поможет? S>Поможет не писать везде Expr | OOM, а встроить OOM в Expr.
И что с ним делать в Expr? Давайте добавим. Как это отразится на expr0? Что вообще дальше делать с этим OOM?
_>>
_>>expr0 = If (Mul ...) ... -- где здесь OOM?
_>>
S>В рамках Haskell, наверное, нигде. Язык не готов к работе в условиях дефицита памяти
Где бы он был в Java?
_>>IO так и делает: бросает исключения без указания в сигнатуре. S>Ну, то есть в IO встроена возможность бросать всё что угодно? откуда в div появилась возможность "бросить" divisionByZero?
Здравствуйте, korvin_, Вы писали: _>Вот интересно, а что вы делаете в Java при получении OOM?
Смотря какой код у меня исполняется. Есть применения, где при получении OOM мне нужно не просто 500 Internal Error, а успеть сделать что-то полезное (например, сохранить стейт воркфлоу) перед тем, как умереть.
_>Там написано не про это. Насчёт does not uglify the calling code я бы поспорил.
Другого описания работы исключений в haskell я не нашёл.
_>А что вы предлагаете делать при OOM?
Много что можно сделать, особенно если озаботиться заранеешным созданием объектов, которые потребуются для обработки OOM. Например, можно урезать осетра и попробовать продолжить с буфером меньшего размера (хоть и медленнее).
_>Это алгебраический тип, но при чём тут это?
То, что OOM могло бы быть одним из его слагаемых.
_>И что с ним делать в Expr? Давайте добавим. Как это отразится на expr0? Что вообще дальше делать с этим OOM?
Обрабатывать или прокидывать.
Ну, вот что нам делать с None в Optional<T>? Как замена int на Maybe<int> отразится на арифметике? Всё зависит от того, как сделан лифтинг в языке — автоматически, монадически, или никак.
_>Где бы он был в Java?
В Java он бы вылетел в виде unchecked exception. В воображаемой Java без исключений, но с not-nullable reference типами мы могли бы заставить new возвращать не T, а T?. В воображаемой Java с алгебраическими типами и наследованием мы могли бы заставить new T возвращать не T, а T | Error, и для общения с кодом, который требует чистый T этот результат пришлось бы очищать.
Здравствуйте, hi_octane, Вы писали:
AD>>Коды ошибок в расте? Интересно... _>Если речь о том что Option или Variant сильно отличаются от return 0 в C, или HRESULT в COM/OLE (тут серая рожа того кто знает ) — то для меня разница исчезающе мала. Современными средствами можно накрутить на почти любой язык проверку что на каждый возврат из функции написан if. То что в Rust такая проверка часть языка — просто деталь реализации.
ну давайте тогда скажем что исключения — это просто подход с goto. Ведь разница исчезающе мала.
Не надо относиться к сахару как шелухе. Он значительно меняет подходы. С — это просто обёртка или сахар над асмом. Но он перевернул программирование.
Так и обработка ошибок в Rust — абсолютно другое. Лишены всех недостатков кодов возратов: многословности и необязательности обработки. Ну как можно говорить что разница исчезающе мала? Да она кардинальна. Без этих недостатков исключения вчистую проигрывают.
Re[14]: Result objects - все-таки победили Exceptions?
Здравствуйте, Sinclair, Вы писали:
S>Читаем дальше: S>
S>Haskell solves the problem a diplomatic way: Functions return error codes, but the handling of error codes does not uglify the calling code.
S>Там написана неправда?
Там нет примеров более сложной (бизнес-)логики, которая использует хотя бы три-четыре библиотеки от разных разработчиков. В статье идеальный пример, когда все "исключения" могут быть определены в виде одного алгебраического типа. Что хорошо работает в примере и очень плохо работает в реальной жизни. Например, тот же
data ApplicationException =
ReadException
| WriteException
instance ThrowsRead ApplicationException where
throwRead = ReadException
instance ThrowsWrite ApplicationException where
throwWrite = WriteException
Является аналогом
final String content;
try {
content = readFile(...);
} catch (IOException e) {
throw new ReadException();
}
final String content;
try {
writeFile(...);
} catch (IOException e) {
throw new WriteException();
}
Информация о том, почему "не смогла", потеряна. Ладно, можно добавить исключение в ApplicationException:
data ApplicationException =
ReadException IOException
| WriteException IOException
Уже лучше. Хотя... Почему у нас WriteProtected является возможной ошибкой для read? Давайте честно:
Все хорошо? Почти. Теперь у нас усложняется copyFile. Начальное определение ниже уже не работает
copyFile src dst =
writeFile dst =<< readFile src
В первом случае у нас InputException, во втором — OutputException. И нужно их к общему типу приводить. Ну да, есть дальше примеры с ReadException/NoReadException. Только там вроде бы все комбинации исключений (ThrowsWrite (ReadException e) и подобные) определяются. Это хорошо для примера. А что мы будем делать в реальном коде, когда исключений может быть больше? Всякие SQLException, MongoException, RedisException. При этом все эти SQL/Mongo/Redis тоже не просто алгебраические типы, а такие же тайпклассы со сложной структурой. Можно применять примеры из статьи, но это же куча ручного кода! Удобства там мало. А уж если мы будем добавлять полиморфизм...
Давайте сделаем теперь consumeFile, которая получает функцию для обработки содержимого файла:
Что мы будем делать в этом случае? И выйдут ли удобочитаемые сигнатуры? И что будет, если у нас там бизнес-логика (те же хранилища, часть из которых — SQL, а другая — Mongo). Можно оставить вывод типов компилятору. Только вот какие будут сообщения об ошибках, если типы не сходятся? Я не уверен, что это можно будет удобно читать.
Ладно, все выше — это прелюдия. Суровая реальность состоит в том, что Haskell — ленивый язык. Вот возьмем какой-нибудь Prelude.readFile. Давайте пока не будем смотреть на то, что она возвращает банальный IO вместо логичного ExceptionalT ReadException IO (). Давайте посмотрим на результат. А в результате у нас ленивая строка, которая читается при необходимости. Теперь возьмем пример take 5 <$> readFile "/dev/zero". Вопрос — где у нас будет исключение, если вдруг произошла ошибка диска? Произошло ли у нас исключение в take 5? Или только в IO, которое получилось после <$>? Первый вариант потребует много переделок. List будет таскать список ошибок, которые могут возникать при доступе. Все функции работы со списком будут параметризованы этим типом исключений. Второй вариант (исключения только в IO) будет плохо работать в более сложных случаях:
Мы можем отличить ошибки шаблона от ошибок данных. Ну, почти. По нашей логике мы не можем отличить ошибки чтения шаблона и чтения данных. Они же происходят в результирующем IO (результате applyFileTemplate) а не в applyTemplate! Как-то это не очень удобно в обработках. Как мы теперь будем понимать, что не прочиталось?
В общем, в Haskell ситуация с ошибками примерно та же, что и в Java, и в C#, и в большинстве остальных языков с исключениями. Коды возврата удобны для случаев, когда ошибка ожидаемая, частая и обрабатывается обычно по месту вызова. А в остальных случаях начинаются пляски и типами и конвертацией. А все "более сложные" случаи заметаются под ковер https://downloads.haskell.org/~ghc/6.10.1/docs/html/libraries/base/System-IO-Error.html.
Re[5]: Result objects - все-таки победили Exceptions?
Здравствуйте, Константин Б., Вы писали:
M>>Как я понял, это код ошибки + полезный результат, упакованное вместе. Те же яйца, вид сбоку
КБ>Неправильно понял. Это не код ошибки, это объект ошибки. Т.е. как исключение только явно возвращаемое из функции. КБ>Совершенно лишенное недостатков кодов ошибок.
Не понял тогда, а как возвращается полезный результат?
Здравствуйте, Shmj, Вы писали:
S>Ну вот рекомендации в новомодных языках: https://docs.flutter.dev/app-architecture/design-patterns/result
S>- все-таки топят за Result objects для бизнес-логики.
S>А ведь это всю цепочку поддерживать. Как-то много лишних букв добавляется.
Для любой логики:
1. негативный результат, в т.ч. логическая ошибка операции — это result object
2. системные ошибки, ситуация "а хер его знает" — исключение
Т.е. должны быть оба этих механизма
Примеры:
1. не получается зарезервировать покупку
2. запрос к сервису фейлится по таймауту, ошибке днс, сервис возвращает html вместо json итд
Если кидать 2й кейс как result object, получаем много геморроя на ровном месте
Если кидать 1й кейс как исключения, полуаем так же много геморроя на ровном месте
Идеальный процессинг, на мой взгляд, такой:
controller_method(args) {
try {
const useCase = new ReserveItemUseCase(args);
return useCase.invoke(); // логические ошибки идут сюда, их логируем по месту возникновения
} catch(exception) {
logger.error({exception}, 'ReserveItemsUseCase');
throw Application.wrap(exception);
}
}
такой try catch при желании можно убрать в generic code, останется только useCase.
Проблема возникает в том случае, например, если логическую операцию нужно откатить или повторить если возникает исключение
В этом случае можно чз утилиту settled, которая возвращает тупл [exception, result]
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, korvin_, Вы писали: _>>Вот интересно, а что вы делаете в Java при получении OOM? S>Смотря какой код у меня исполняется. Есть применения, где при получении OOM мне нужно не просто 500 Internal Error, а успеть сделать что-то полезное (например, сохранить стейт воркфлоу) перед тем, как умереть.
Это возможно только если под этот твой процесс сохранениня стейта уже была выделена вся необходимая для его работы память, что в языках с GC практически невозможно сделать, соответственно твоё "сделать что-то полезное" точно так же наткнётся на ООМ.
_>>А что вы предлагаете делать при OOM? S>Много что можно сделать, особенно если озаботиться заранеешным созданием объектов, которые потребуются для обработки OOM. Например, можно урезать осетра и попробовать продолжить с буфером меньшего размера (хоть и медленнее).
Это в теории, на практике -- нет. Даже в С/C++ и прочих языках с "ручным" контролем памяти что-то сделать сложно. Поэтому приходит OOM-Killer
Поэтому на практике, что можно сделать: упасть. Гипервизор/оркерстратор перезапустит ноду/сервис/под/контейнер, а мы будем изучать логи, метрики, дампы, и по результату, либо добавим больше памяти, либо изменим параметры масштабирования, либо починим утечку, либо оптимизируем код.
_>>Это алгебраический тип, но при чём тут это? S>То, что OOM могло бы быть одним из его слагаемых.
Так и что бы это дало? Как его использовать? Напиши пример.
_>>И что с ним делать в Expr? Давайте добавим. Как это отразится на expr0? Что вообще дальше делать с этим OOM? S>Обрабатывать или прокидывать. S>Ну, вот что нам делать с None в Optional<T>? Как замена int на Maybe<int> отразится на арифметике? Всё зависит от того, как сделан лифтинг в языке — автоматически, монадически, или никак.
Так Optional -- это другой тип по отношению к int. Теперь, сам Optional же тоже требует памяти, значит Optional<Optional<T>>, и для него тоже нужна память, Optional<Optional<Optional<T>>>, и так далее.
_>>Где бы он был в Java? S>В Java он бы вылетел в виде unchecked exception. В воображаемой Java без исключений, но с not-nullable reference типами мы могли бы заставить new возвращать не T, а T?. В воображаемой Java с алгебраическими типами и наследованием мы могли бы заставить new T возвращать не T, а T | Error, и для общения с кодом, который требует чистый T этот результат пришлось бы очищать.
Так вот в Haskell/Ocaml и т.п. у нас нет new T, у нас сразу T, как 1, 2, true, false, "foo", "bar". Сразу значение.