Conversation
|
Could this be extended to handle |
|
@hcarty, we'll probably have to disallow exception under or patterns such as |
|
@hhugo: the first scheme I gave handles exceptions under or patterns. Your example match+ expr with
| None | exception Not_found -> case1
| Some _ -> case2is translated to (match+)
(function Ok None | Error Not_found -> case1 | Ok Some _ -> case2)
(match expr with x -> Ok x | exception e -> Error e)The second scheme can also handle a few cases of exceptions under or patterns, but it needs to either duplicate the continuation expressions (i.e. One additional issue with handling exceptions is exhaustiveness: since exception cases (like |
|
I like this proposal. I have a ppx extension (not published) that performs the same translation, because I quite often write code like this in MetaOCaml programs match_ x @@ function
| Some false -> .< e1 >.
| Some true -> .< e2 >.
| None -> .< e3 >.and prefer to write something closer to the standard match syntax, without explicit use of higher-order functions. Comments:
type (_,_) eql = Refl : ('a,'a) eql
let cast : type a b. (a,b) eql -> a -> b =
fun e x -> match e with Refl -> xIn the above code, it doesn't seem possible to rewrite the body of |
|
Do you think this will make it into the next release? It would be very helpful to have the If they won't both land for 4.08.0, is it worth delaying both to a later release so they can be adopted by the community together? |
|
@lpw25 For the moment, we have merged let operators but not this one. Are you satisfied with this state? |
|
Yes, I think that is fine for now. |
|
At Issuu, we use |
|
Coming to this PR after a while:
|
|
An option would be to elaborate differently depending on whether there is a exception pattern or not. We'd need an extra class of identifiers (exception match+)
(match expr with m -> Ok m | exception exn -> Error exn)
(function Ok None | Error Not_found -> case1 | Ok Some _ -> case2 | Error exn -> raise exn)and without the exception case (match+)
expr
(function None -> case1 | Some _ -> case2)Exceptionless monads would omit let (match+) m f =
(exception match+)
(Ok m)
(function Ok x -> f x | Error exn -> raise exn) |
|
I like the idea, thanks! What about This being said, there is something that bugs me about the choice of elaboration on the scrutinee ( return : 'a -> 'a option
val fail : 'a option
val (match+ with exception): 'a option -> (('a, unit) result -> 'b) -> 'b
let (match+ with exception) m f =
match f with
| Some v -> f (Ok v)
| None -> f (Error ())
(* example usage *)
val map : ('a -> 'b) -> 'a option -> 'b option
let map f m = match+ m with
| x -> return (f x)
| exception () -> failNote that here the translation I expect is that the branches are made result-expecting, but the scrutinee itself is passed unchanged. Does that make sense? (@yallop?) |
|
My plan was to allow binding things like match* e1 with
| p1 ->e2
| exception* p2 -> e3into: (match*) ((exception*) e1)
(function
| Ok p1 -> e2
| Error p2 -> e3)Intended to be used with functions of type: val (exception*) : 'a t -> ('a, foo) result tThis fits in well with how Possibly we should be thinking in more terms general terms. |
Could you give an example that doesn't work with this scheme? |
|
I just meant that my proposal suggests that you might be able to write something like: match* e1 with
| p1 -> e2
| exception++ p2 -> e3
| exception-- p3 -> e4with two different |
|
A way to translate different exception operators would be (match*)
((exception--) ((exception++) e1))
(function
| Ok (Ok p1) -> e2
| Ok (Error p2) -> e3
| Error p3 -> e4)The order of application can be takes as the order of occurrence, since the matches will be disjoint and therefore can be ordered by the user as needed, but it's probably better that the implementation ensure they commute. |
|
@gasche I agree |
|
Is there a compelling reason to support separate operators for each exception pattern when we don't have separate operators for each value pattern? The fact that we have a keyword for exception patterns but not value patterns is more an accident of history than a fundamental property of matching. (We almost ended up doing things the other way around.) |
|
As a user, I would expect an Also, will this interact with effect handler syntax? |
Not really. I was more pointing out that my proposed scheme kind of implies that you can write such a thing. If we do use my scheme then I'd prefer that we insist that all exception operators in a match are the same, and just error otherwise.
This is a match applied to a monadic computation, not a computation in OCaml's implicit monad. The "exceptions" that it catches should be operations of the monad used in the computation. A number of monads have an operation that is isomorphic to catching an exception, not all of them use the
It would be a bit difficult to make the typed effect syntax available for use with monadic computations, and I don't know of any monads which provide effect handling operations. (Possibly something from the "scoped effects" line of work would.) So I don't think we'll extend this support to work with effect syntax. |
|
What happens when the exception matching is incomplete, like it normally is? If the elaborated pattern matching raises a regular exception, val (exception*) : 'a t -> ('a, error) result t
val (raise*) : error -> 'a tAnd should an undecorated match* expr with
| v0 -> Some v0
| exception* Not_found -> None
| exception Failure _ -> Noneelaborates to match expr with
| exception Failure _ -> None
| m ->
(match*)
((exception*) m)
(function
| Ok v0 -> Some v0
| Error Not_found -> None
| Error err -> (raise*) err) |
ExamplesI wish we would discuss with more concrete examples of algebraic structures using these operators, with implementations of the operators and use-cases for them. So far we have exactly one example (#1955 (comment)). using TypesI have the impression that
On the other hand, it raises pesky syntactic questions -- one nice thing with (raise*)I find @paurkedal proposal to have a re-raising operation interesting, but note that the question also arise for value matches: some monads have a natural notion of failure that is better than raising a |
I don't like this proposal. I don't think the notion of "monad whose computations can also raise native exceptions" is sensible. What we have in certain libraries is an interplay of two sorts of effects, those captured by the monad (possibly with some notion of failure) and "native" failures (exceptions). If you want to play at the two levels at once, you have to deal with two sets of operators (just like you mix code from two different monads); this is conceptually more regular than trying to have a single operator play at the two levels. Note that the proposed desugaring is also not expressive enough to cover all of the |
The point was, on the contrary, to decouple monadic failure and regular exceptions. The
If I understand correctly, I agree this will not cover the Lwt approach of automatically converting regular exceptions to monadic failure. I suspect we don't want to do this directly in the elaborated code, since it would restrict |
|
Out of curiosity, what is the status of this PR? |
|
I'd like to finish it off, including support for either |
|
15 months later, does someone else wishes to push this idea forwards? Personally I'm not a big fan of the proposed feature, but could still be convinced. |
|
Let's just close it. Someone can always pick it up again later. |
This PR is the "match operators" part of #1947. I've extracted it to allow this part to be discussed independently of the rest of that PR. I've copied the description of this aspect below.
This PR also adds support for match operators. These are simpler than let operators because there is no match ... and ... construct. The translation for these is simply:
match+ expr with
| pat1 -> case1
| pat2 -> case2
to:
(match+) (function | pat1 -> case1 | pat2 -> case2) expr
As with let I would recommend that a monad bind match+ to map and match* to bind.