Idiomatic exceptions for exiting loops in OCaml -


in ocaml, imperative-style loops can exited raising exceptions.

while use of imperative loops not idiomatic per se in ocaml, i'd know idiomatic ways emulate imperative loops exits (taking account aspects such performance, if possible).

for instance, an old ocaml faq mentions exception exit:

exit: used jump out of loops or functions.

is still current? standard library mentions general-purpose exception:

the exit exception not raised library function. provided use in programs.

relatedly, this answer question mentions using precomputed let exit = exit exception avoid allocations inside loop. still required?

also, 1 wants exit loop specific value, such raise (leave 42). there idiomatic exception or naming convention this? should use references in case (e.g. let res = ref -1 in ... <loop body> ... res := 42; raise exit)?

finally, use of exit in nested loops prevents cases 1 exit several loops, break <label> in java. require defining exceptions different names, or @ least using integer indicate how many scopes should exited (e.g. leave 2 indicate 2 levels should exited). again, there approach/exception naming idiomatic here?

as posted in comments, idiomatic way exit in ocaml using continuations. @ point want return go to, create continuation, , pass code might return early. more general labels loops, since can exit has access continuation.

also, posted in comments, note usage of raise_notrace exceptions trace never want runtime generate.

a "naive" first attempt:

module continuation : sig   (* flaw approach: there no choice      result type. *)   type 'a cont = 'a -> unit    (* with_early_exit f passes function "k" f. if f calls k,      execution resumes if with_early_exit completed      immediately. *)   val with_early_exit : ('a cont -> 'a) -> 'a end =  struct   type 'a cont = 'a -> unit    (* return implemented throwing exception. ref      cell used store value continuation      called - way avoid having generate exception      type can store 'a each 'a module used with.      integer supposed unique identifier distinguishing      returns different nested contexts. *)   type 'a context = 'a option ref * int64   exception unwind of int64    let make_cont ((cell, id) : 'a context) =     fun result -> cell := result; raise_notrace (unwind id)    let generate_id =     let last_id = ref 0l in     fun () -> last_id := int64.add !last_id 1l; !last_id    let with_early_exit f =     let id = generate_id () in     let cell = ref none in     let cont : 'a cont = make_cont (cell, id) in     try       f cont     unwind when = id ->       match !cell       | result -> result         (* should never happen... *)       | none        -> failwith "with_early_exit" end    let _ =   let nested_function k = k 15; in    continuation.with_early_exit (nested_function 42)   |> string_of_int   |> print_endline 

as can see, above implements exit hiding exception. continuation partially applied function knows unique id of context created, , has reference cell store result value while exception being thrown context. code above prints 15. can pass continuation k deep want. can define function f @ point passed with_early_exit, giving effect similar having label on loop. use often.

the problem above result type of 'a cont, arbitrarily set unit. actually, function of type 'a cont never returns, want behave raise – usable type expected. however, doesn't work. if type ('a, 'b) cont = 'a -> 'b, , pass down nested function, type checker infer type 'b in 1 context, , force call continuations in contexts same type, i.e. won't able things like

(if ... 3 else k 15) ... (if ... "s" else k 16) 

because first expression forces 'b int, second requires 'b string.

to solve this, need provide function analogous raise return, i.e.

(if ... 3 else throw k 15) ... (if ... "s" else throw k 16) 

this means stepping away pure continuations. have un-partially-apply make_cont above (and renamed throw), , pass naked context around instead:

module bettercontinuation : sig   type 'a context    val throw : 'a context -> 'a -> _   val with_early_exit : ('a context -> 'a) -> 'a end =  struct   type 'a context = 'a option ref * int64   exception unwind of int64    let throw ((cell, id) : 'a context) =     fun result -> cell := result; raise_notrace (unwind id)    let generate_id = (* same *)    let with_early_exit f =     let id = generate_id () in     let cell = ref none in     let context = (cell, id) in     try       f context     unwind when = id ->       match !cell       | result -> result       | none        -> failwith "with_early_exit" end    let _ =   let nested_function k = ignore (bettercontinuation.throw k 15); in    bettercontinuation.with_early_exit (nested_function 42)   |> string_of_int   |> print_endline 

the expression throw k v can used in contexts different types required.

i use approach pervasively in big applications work on. prefer regular exceptions. have more elaborate variant, with_early_exit has signature this:

val with_early_exit : ('a context -> 'b) -> ('a -> 'b) -> 'b 

where first function represents attempt something, , second represents handler errors of type 'a may result. variants , polymorphic variants, gives more explicitly-typed take on exception handling. powerful polymorphic variants, set of error variands can inferred compiler.

the jane street approach same described here, , in fact had implementation generated exception types first-class modules. not sure anymore why chose 1 – there may subtle differences :)


Comments

Popular posts from this blog

powershell Start-Process exit code -1073741502 when used with Credential from a windows service environment -

twig - Using Twigbridge in a Laravel 5.1 Package -

c# - LINQ join Entities from HashSet's, Join vs Dictionary vs HashSet performance -