As with previous comments I think the following pattern is quite nice for writing that out. Sure you need to pass in the values as tuples, but it’s jut too short and sweet.
let f = function | 1, _, _ | _, 2, _ -> 0
| _, _, z -> z
let () =
Printf.printf "%d\n" (f (1,0,9));
Printf.printf "%d\n" (f (3,2,9));
Printf.printf "%d\n" (f (0,0,9))
Thank you! If I understand correctly, this trade-off is making concessions for what is, in the case of this particular pattern, a programming error that invalidates the pattern altogether. So my impression is that an implementation of with_return that won’t clear the backtrace is fine.
Perhaps a case could be made to calling the escaping function break instead of return? Here are two arguments to support it:
return could be mistaken for escaping the boundary of the entire function body, as is usually the case in languages having such keyword
I actually found examples using the Result or Or_error monad but where you are interested in an early return for the Ok case somewhere deep in the chain. The issue there would be that return is already in scope and used by the monad. Regardless if you have surrounding code that did some open M.Let_syntax using a different keyword would probably make things more clear.
I don’t feel strongly, this is simply reflecting as I am going through the cases I found in my code base that are currently using With_return.
In general it is more ergonomic to make the early return instruction sufficiently polymorphic to allow it to be used several times in contexts expecting different types.
Using the implementation as stated, this won’t type check for example:
let f x =
with_return (fun ~return ->
let (_ : int) = if x = 0 then return () else x in
let hello = if x = 1 then return () else "hello" in
print_endline hello)
;;
Line 4, characters 48-55:
Error: This constant has type string/2 but an expression was expected of type
int
File "_none_", line 1:
Definition of type string/2
whereas with Base.With_return.with_return it will. The latter uses a higher order polymorphism argument using a record field, however I think it will soon be possible to do with regular a argument? (I think there is an related entry in the next ocaml changelog).
When I have (infrequently) used this idiom to provide early returns, I have simply called it ‘escape’, and the applying function ‘with_escape’, as in call-with-escape-continuation (call/ec).
I proposed an RFC with syntactic sugar for early break/return using “exception functions”, see ocaml/RFCs#11740.
Usage examples:
let exception break () = () in
while <cond> do
... break () ...
done
while <cond> do
let exception continue () = () in
... continue () ...
done
let findi p arr =
let exception found i = Some i in
for i = 0 to Array.length arr - 1 do
if p arr.(i) then found i
done;
None
Everyone that gave feedback complained, so I closed it.
Well I suggested to add the actual control flow structures in 2019 and nobody was interested . I still think it’d be great to have proper imperative control flow structures… But at this point the core language is mostly frozen, isn’t it? We’re not going to get Rust’s loop, or if let, or break/continue, or safer syntax.
I wonder if there’d be a nice way, in the status quo, to tell the compiler about our intent, like [@tailcall] did. Something like an attribute that emits a warning if a local exception does allocate or leak.
We have let[@local] f x = ... which has similar semantics, so I think it would make sense to support let[@local] exception Foo.
(I think that exception functions are nicer than break and continue for OCaml because they generalize early-exit in a flexible way, in particular they make it relatively clear what the control-flow scope is and how to return another value than ().)
We have let[@local] f x = ... which has similar semantics, so I think it would make sense to support let[@local] exception Foo.
let[@local] exception would be great, I think!
(I think that exception functions are nicer than break and continue for OCaml because they generalize early-exit in a flexible way, in particular they make it relatively clear what the control-flow scope is and how to return another value than ().)
That’s a really neat thing about Rust: loop { … } is an expression and
it has a non-empty type iff it contains break, possibly with an
argument.
It seems that 5.4 (or 5.3?) started typing while true do … done as 'a instead of unit, so we’re getting closer to this in a way