Как писать защитные программы с помощью purescript Aff

Я как бы новичок в purescript, и я экспериментировал с эффектами и определенными асинхронными эффектами.

Одна из вещей, которые мне больше всего нравятся в FP и строгих компиляторах, таких как purescript, - это то, что он заставляет вас обрабатывать все возможные результаты, в частности, когда вы определяете, что что-то может выйти из строя. Если вы, например, используете Either, вам нужно указать программе, что делать, если у вас есть Right ответ или ошибка.

Когда я впервые посмотрел на эффекты, мне понравилась концепция actions и handlers, и что, если часть вашего кода должна генерировать исключение (я полагаю, что это последний ресурс, который вы хотите использовать), вам нужно объявить его, используя что-то вроде

someAction :: forall eff. Eff (exception :: EXCEPTION | eff)

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

Но, проведя несколько базовых тестов с монадой Aff и библиотекой purescript-node-fs-aff, я получил несколько неожиданных результатов.

Если я сделаю что-то подобное

main :: forall e. Eff (console :: CONSOLE, buffer :: BUFFER, fs :: FS | e) Unit
main = do
  _ <- launchAff $ readAndLog "./non-existing-file" 
  pure unit

readAndLog :: forall eff. String -> Aff (fs :: FS, console :: CONSOLE | eff) Unit
readAndLog path = do
  str <- readTextFile UTF8 path
  log str

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

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

main :: forall e. Eff (console :: CONSOLE, buffer :: BUFFER, fs :: FS | e) Unit
main = do
  _ <- launchAff $ readAndLog "./non-existing-file" `catchError` (\e -> log ("buu: " <> message e))
  pure unit

readAndLog :: forall eff. String -> Aff (fs :: FS, console :: CONSOLE | eff) Unit
readAndLog path = do
  str <- readTextFile UTF8 path
  log str

В идеале я хотел бы сделать что-то вроде Either и нести ответственность за обработку конкретных ошибок, которые могут возникнуть в этой операции. Например, когда я читаю файл, я должен ожидать появления ошибки типа ENOENT (файл не существует) или EACCES (у вас нет доступа) и т. Д. Если я хочу проигнорировать конкретную причину и просто записать, что это не удалось, это мой выбор, но система типов должна заставить меня справиться с этим.


person Hernan Rajchert    schedule 21.03.2018    source источник
comment
В канале кошелька я упоминал об этой проблеме github.com/slamdata/purescript-aff/ issues / 137   -  person Hernan Rajchert    schedule 21.03.2018
comment
Проблема действительно в том, что launchAff глотает ошибки. Он должен требовать от вызывающего пользователя предоставить инструкции (то есть обратного вызова) о том, что делать с ошибками, но вместо этого он просто игнорирует их.   -  person Fyodor Soikin    schedule 21.03.2018


Ответы (2)


У вашего вопроса много граней.

Во-первых, строки эффектов скоро будут удалены из стандартной практики. Это началось с v0.12. Вот опрос общественного мнения по этому поводу. Обсуждения ведутся на GitHub. В двух словах: бремя перевешивает пользу.

Затем возникает проблема понимания того, какие исключения могут быть выброшены. Если вы где-то используете сторонний JavaScript, это будет сложно. В этом случае я рекомендую рассматривать известные исключения как нижнюю границу всех исключений, которые могут быть выброшены. Другими словами, даже если вы перехватываете все известные исключения, вы также должны сделать перехват по умолчанию для учета неизвестного.

Взгляните на библиотеку Натана Фобиона purescript-checked-exceptions . Это показывает, как применять отмеченные исключения к интерфейсу ExceptT.

Я проделал аналогичную работу для собственных исключений JavaScript, которые подходят для FFI. К сожалению, это еще не опубликовано, но я надеюсь, что скоро смогу это сделать. По крайней мере, вы можете знать, что это возможно.

Последний аспект - это сама Aff. К сожалению, Aff не предназначен для произвольных исключений. Он поддерживает только тип Error (то есть одноименный тип JavaScript). Поэтому лучше использовать purescript-checked-exceptions с Aff для добавления проверенных исключений.

person erisco    schedule 21.05.2018
comment
Спасибо за отличный комментарий! Жаль, что они убирают строки эфф, мне понравилась эта функция. Я решил не использовать Aff в основном потому, что он рассматривает все ошибки как ошибки, и мне нравятся конкретные, более описательные ошибки. Не для js-кода, запускаемого через FFI, а для моего собственного кода. - person Hernan Rajchert; 23.05.2018
comment
@HernanRajchert ExceptV - это преобразователь монад. Следовательно, вы можете сложить его с Aff, чтобы добавить проверенные исключения. В этом случае вы можете по сути игнорировать возможности Aff для обработки ошибок. Если вам нужно расписание, Aff по-прежнему отличный выбор. - person erisco; 24.05.2018

Компилятор не сообщит вам об этом, потому что исключения ассимилируются Aff оборудованием в используемой вами библиотеке. Вот соответствующие части:

readTextFile = toAff2 A.readTextFile
toAff2 f a b = toAff (f a b)
toAff p      = makeAff \e a -> p $ either e a

Где A.readTextFile - это async-версию в библиотеке Node.

Выражение either e a - вот где это происходит. Поскольку Aff имеет MonadError, ваш единственный выход - действительно поймать ошибку. Тем не менее, я полностью согласен с вами, что эффект должен раскрывать факт исключения фактов. По какой-то причине только синхронная версия библиотеки Node.

person Regis Kuckaertz    schedule 21.03.2018
comment
Из того, что я мог понять, говоря в сообществе Slack, причина того, что launchAff не имеет эффекта исключения, заключается в том, что исключение возникает асинхронно. - person Hernan Rajchert; 21.03.2018
comment
Это все еще похоже на оплошность, не так ли? Ничто не мешает команде Aff предоставить тип для обозначения асинхронных эффектов, например foreign import data AsyncEffect :: # Effect -> Effect. - person Regis Kuckaertz; 22.03.2018