Как стать автором
Обновить

Комментарии 19

В целом, да. Добавлю только, что проблемы с исключениями - чисто архитектурные и присутствуют во многих языках, где вообще встаёт вопрос "возвращать или ловить?". В своём коде всегда ловлю вызовы внешних библиотек, которые и так обычно в чистом IO a работают, и непосредственно на верхнем уровне - в main или forkFinally, на случай, если где-то всё же упустил. Все внутренние вызовы всегда вокруг ExceptT/MonadError построены.

Хорошая, вдумчивая статья (не всю пока прочитал — в процессе). Давно не читал на Хабре чего-то, что мне бы понравилось. Спасибо!

Хорошая статья, спасибо!
Жаль, что на Хабре про Haskell пишут крайне редко.

Автору -- респект и пожелание писать ещё!

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

Не понимаю, о чём вы, но кликбейт неплохой. Если с чем и есть некоторые проблемы, так это со стандартным трейтом Error. Но никто не говорит, что надо его использовать везде.

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

Опять же в 95% случаев ошибка это просто информация о некорректных входных данных (в довольно широком смысле). И всё, что вы можете сделать в этом случае - внятно сообщить об этом пользователю.

Кстати, полагаю, сейчас центральным местом в обработке ошибок становится даже не сам Result, а трейт Try. При этом Result всё ещё остаётся подходящим для подавляющего числа случаев. Для некоторых других случаев есть Option и ControlFlow.

Проблема Result в том, что когда они используется, как есть (без box Error, failure, anyhow), то он не композится с другими Result. Именно поэтому и придумали failure и anyhow. Однако их проблема уже в другом. Как только ты забоксил ошибку в трейт Error (или обернул в anyhow::Error), ты понятия не имеешь, что за ошибка лежит внутри. Два стула: либо не композится, как MonadError/ExceptT либо неизвестность, как с голыми исключениями.

Если не ошибаюсь, anyhow::Result используется только в приложениях и конечном киентском коде, поэтому его и композить нет необходимости. И не очень понятно, как предлагается композить два чёрных ящика, кроме как создать ещё один ящик и положить эти два туда. Вот этот момент мне не совсем понятен.

Добавлю, чтобы не писать лишний комментарий: хорошая информативная статья ?

Наверное, я как-то криво выразился. Не нужно композить два чёрных ящика (box Error, с anyhow::Error). Нужно композить два белых (конкретные Result<FooError, _>, Result<BarError, _>) в один чёрный. И как только вы это сделали, объединили Result<FooError, T> с Result<BarError, T> в Result<anyhow::Error, T>, вы потеряли вообще всю информацию, какие конкретно ошибки могут там быть. Это всё равно, что хаскелевый SomeException

А зачем вообще композить в "чёрный" anyhow::Error, если информация об ошибке всё ещё нужна?


#[derive(Error, Debug)]
pub enum AppError {
    Foo(#[from] FooError),
    Bar(#[from] BarError),
}

А Вы точно читали этот текст дальше наброса на раст?

Он без проблем композится с другими Result если настроено отношение From.

Оператор ? это по большей части сахар для следующей конструкции (версия для Result):

match some_expr {
    Ok(ok_value) => ok_value,
    Err(err_value) => return From::from(err_value),
}

Обратите внимание на вызов From::from. Это вызов преобразования из одного типа ошибки в другой. Так что для того, чтоб композитить это всё, нужно только реализовать From для своего типа ошибки. thiserror делает этот процесс простым и приятным. При этом не теряя явности.

А для случая, когда нужно кастовать один Result в другой Result есть такие варианты:

fn foo<T>(result: Result<T, A>) -> Result<T, B> {
  Ok(result?)
}

fn bar<T>(result: Result<T, A>) -> Result<T, B> {
  result.map_err(From::from)
}

impl From<A> for B {
  fn from(a: A) -> B { .. }
}

// не существует реализации impl<T, A, B: From<A>> From<Result<T, A>> for Result<T, B>
// причина тому конфликт с impl<T> From<T> for T

В случае anyhow можно посмотреть внутрь при помощи downcast, в случае thiserror будет работать стандартный match. Крейт failure слишком устарел для того, чтоб упоминать его.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Можно equational constraint добавить

А куда ты его добавишь-то?

Я, кстати, не вижу фундаментальных причин, по которым HasCatch из capability

Функциональные зависимости всё портят. Хотя в этом случае я даже не особо понимаю, как оно там считается

data Foo = Foo deriving (Show, Exception)
data Bar = Bar deriving (Show, Exception)

throwCap :: forall a m b. Cap.HasThrow a a m => a -> m b 
throwCap = Cap.throw @a

type CanThrow a = Cap.HasThrow a a

foo :: (CanThrow Bar m, CanThrow Foo m) => m ()
foo = do
  () <- throwCap Foo
  throwCap Bar

catchCap :: (Unlift.MonadUnliftIO m, Exception e) => Cap.MonadCatch e m a -> (e -> m a) -> m a
catchCap (Cap.MonadCatch m) = Unlift.catch m

baz :: CanThrow Bar m => m ()
baz = foo `catchCap` \Foo -> pure ()
    • Couldn't match type ‘Foo’ with ‘Bar’
        arising from a functional dependency between:
          constraint ‘HasThrow Bar Bar (MonadCatch Foo m)’
            arising from a use of ‘foo’
          instance ‘HasThrow tag e (MonadCatch e m1)’ at <no location info>
    • In the first argument of ‘catchCap’, namely ‘foo’
      In the expression: foo `catchCap` \ Foo -> pure ()
      In an equation for ‘baz’: baz = foo `catchCap` \ Foo -> pure ()
    |
xxx | baz = foo `catchCap` \Foo -> pure ()
    |       ^^^

НЛО прилетело и опубликовало эту надпись здесь

Можно для конкретики пример, когда оно там не работает транзитивно?

Так ето ты скопипасти, да проверь, что оно неюзабельно, куда ни добавляй)

Пробовал?

НЛО прилетело и опубликовало эту надпись здесь

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации