Pull to refresh

Comments 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 слишком устарел для того, чтоб упоминать его.

UFO just landed and posted this here
UFO just landed and posted this here

Можно 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 ()
    |       ^^^

UFO just landed and posted this here

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

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

UFO just landed and posted this here

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

Sign up to leave a comment.

Articles