Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does it make sense to include support for try? #8

Closed
jordwalke opened this issue Aug 24, 2018 · 3 comments
Closed

Does it make sense to include support for try? #8

jordwalke opened this issue Aug 24, 2018 · 3 comments

Comments

@jordwalke
Copy link

I was comparing ppx_let with lwt's ppx extension and I was curious if it makes sense for ppx_let to include support for try, where a module that defines bind and map are also able to support transforms for handling exceptions.

@yminsky
Copy link

yminsky commented Aug 24, 2018

I don't think so. Error handling semantics differ a lot between different monads, and I suspect this would just lead to surprising and hard to debug results.

@jordwalke
Copy link
Author

Fair enough. Libraries can provide their own handlers that thread through error handling partially recreating the behavior of exception handling.

@texastoland
Copy link

texastoland commented Sep 9, 2018

@yminsky @lpw25 I've been giving this a lot of thought because of the linked Reason issue. Would it make sense to optionally include a Let_syntax.Error based on MonadError?

module type MONAD_ERROR = sig
  type e
  val catchError : 'a t -> f:(e -> 'a t) -> 'a t
  val throwError :            e -> 'a t
end

catchError would map to let%try (or similar) and throwError to RHS binding. MonadError is a lightweight generalization of any type that can represent failure whether option, result, Lwt.result, Deferred.Or_error.t, etc.

I don't think its shortcomings apply to OCaml. Tl;dr the signatures of catchError and throwError don't reflect removal or addition (respectively) of effects but idiomatic OCaml doesn't track side effects either. Algebraic "effect arrows" will ultimately capture that more robustly without extra HKTs (e.g. in ErrorControl).

I'm not implying let%try should rewrite exceptions like lwt%try. I agree with the reasoning in the Async API docs.

A function whose type ends with ... -> 'a Deferred.Or_error.t and still raises should be considered broken, and be fixed. With that property in mind, Deferred.Or_error.List.iter, for example, does not wrap the execution of the given iter function f inside a monitor. If one of these application raises, the whole function Deferred.Or_error.List.iter will raise as a way to try to alert the developer that one the function is broken and needs attention and fixing, rather than silently catching the error and converting it to Or_error.Error.

If some monad needs that behavior it can still implement it by catching inside each method (map, bind, and both or apply) and encoding the exception as an error. More generally a functor (in the OCaml sense) could create such a monad for any MONAD_ERROR module.

I'm not aware of any precedent (Haskell, Scala, F#, PureScript, etc.) for sugaring MonadError methods. The use case is to thread error handling inside a monadic computation. For example a failed service request could retry a different data source immediately instead of outside the let% context.

let%bind ctx = get_context
let%bind cxn = let%try
  connect_to_db ctx
with
  | _ -> connect_to_backup ctx
let%bind stats = get_stats cxn

Note the signatures of bind and catchError.

val bind       : 'a t -> f:('a -> 'b t) -> 'b t
val catchError : 'a t -> f:( e -> 'a t) -> 'a t

Their symmetry is intentional. Consider exn result.

val bind       : 'a exn result -> f:('a  -> 'b exn result) -> 'b exn result
val catchError : 'a exn result -> f:(exn -> 'a exn result) -> 'a exn result

catchError is just bind on type e ('a doesn't change) thus desugaring would mirror bind.

(* from *)
let%try M with P -> E
(* to *)
catch M ~f:(function P -> E)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants