# HG changeset patch # User wenzelm # Date 1696792513 -7200 # Node ID b2216709a83911eef5fbf964999e8831ee727e1b # Parent 39498d504f4314aa73588e1a5f5d1a3009ab7715 update documentation on Isabelle/ML exceptions; diff -r 39498d504f43 -r b2216709a839 src/Doc/Implementation/ML.thy --- a/src/Doc/Implementation/ML.thy Sun Oct 08 19:17:41 2023 +0200 +++ b/src/Doc/Implementation/ML.thy Sun Oct 08 21:15:13 2023 +0200 @@ -1070,8 +1070,13 @@ text \ The Standard ML semantics of strict functional evaluation together with - exceptions is rather well defined, but some delicate points need to be - observed to avoid that ML programs go wrong despite static type-checking. + exceptions is rather well defined, but some fine points need to be observed + to avoid that ML programs go wrong despite static type-checking. + + Unlike official Standard ML, Isabelle/ML rejects catch-all patterns in + \<^ML_text>\handle\ clauses: this improves the robustness of ML programs, + especially against arbitrary physical events (interrupts). + Exceptions in Isabelle/ML are subsequently categorized as follows. \ @@ -1100,15 +1105,15 @@ paragraph \Program failures.\ text \ There is a handful of standard exceptions that indicate general failure - situations, or failures of core operations on logical entities (types, - terms, theorems, theories, see \chref{ch:logic}). + situations (e.g.\ \<^ML>\Fail\), or failures of core operations on logical + entities (types, terms, theorems, theories, see \chref{ch:logic}). These exceptions indicate a genuine breakdown of the program, so the main - purpose is to determine quickly what has happened where. Traditionally, the - (short) exception message would include the name of an ML function, although - this is not strictly necessary, because the ML runtime system attaches - detailed source position stemming from the corresponding \<^ML_text>\raise\ - keyword. + purpose is to determine quickly what has happened in the ML program. + Traditionally, the (short) exception message would include the name of an ML + function, although this is not strictly necessary, because the ML runtime + system attaches detailed source position stemming from the corresponding + \<^ML_text>\raise\ keyword. \<^medskip> User modules can always introduce their own custom exceptions locally, e.g.\ @@ -1129,24 +1134,23 @@ program. Such an interrupt can mean out-of-memory, stack overflow, timeout, internal signaling of threads, or a POSIX process signal. An Isabelle/ML program that intercepts interrupts becomes dependent on physical effects of - the environment. Even worse, exception handling patterns that are too - general by accident, e.g.\ by misspelled exception constructors, will cover - interrupts unintentionally and thus render the program semantics - ill-defined. - - Note that the Interrupt exception dates back to the original SML90 language - definition. It was excluded from the SML97 version to avoid its malign - impact on ML program semantics, but without providing a viable alternative. - Isabelle/ML recovers physical interruptibility (which is an indispensable - tool to implement managed evaluation of command transactions), but requires - user code to be strictly transparent wrt.\ interrupts. - - \begin{warn} - Isabelle/ML user code needs to terminate promptly on interruption, without - guessing at its meaning to the system infrastructure. Temporary handling of - interrupts for cleanup of global resources etc.\ needs to be followed - immediately by re-raising of the original exception. - \end{warn} + the environment (e.g.\ via \<^ML>\Exn.capture\ without subsequent + \<^ML>\Exn.release\). + + Note that the original SML90 language had an \<^verbatim>\Interrupt\ exception, but + that was excluded from SML97 to simplify ML the mathematical semantics. + Isabelle/ML does support physical interrupts thanks to special features of + the underlying Poly/ML compiler and runtime system. This works robustly, + because the old \<^ML_text>\Interrupt\ constructor has been removed from the + ML environment, and catch-all patterns \<^ML_text>\handle\ are rejected. + Thus user code becomes strictly transparent wrt.\ interrupts: physical + events are exposed to the toplevel, and the mathematical meaning of the + program becomes a partial function that is otherwise unchanged. + + The Isabelle/ML antiquotation @{ML_antiquotation try}, with its syntactic + variants for \<^ML_text>\catch\ or \<^ML_text>\finally\, supports + intermediate handling of interrupts and subsequent cleanup-operations, + without swallowing such physical event. \ text %mlref \ @@ -1160,11 +1164,11 @@ @{define_ML Runtime.exn_trace: "(unit -> 'a) -> 'a"} \\ \end{mldecls} - \<^descr> \<^ML>\try\~\f x\ makes the partiality of evaluating \f x\ explicit via the - option datatype. Interrupts are \<^emph>\not\ handled here, i.e.\ this form serves - as safe replacement for the \<^emph>\unsafe\ version \<^ML_text>\(SOME\~\f + \<^descr> \<^ML>\try\~\f x\ makes the partiality of evaluating \f x\ explicit via + the option datatype. Interrupts are \<^emph>\not\ handled here, i.e.\ this form + serves as safe replacement for the \<^emph>\fragile\ version \<^ML_text>\(SOME\~\f x\~\<^ML_text>\handle _ => NONE)\ that is occasionally seen in books about - SML97, but not in Isabelle/ML. + SML97. \<^descr> \<^ML>\can\ is similar to \<^ML>\try\ with more abstract result. @@ -1172,11 +1176,13 @@ raised indirectly via the \<^ML>\error\ function (see \secref{sec:message-channels}). - \<^descr> \<^ML>\Fail\~\msg\ represents general program failures. - - \<^descr> \<^ML>\Exn.is_interrupt\ identifies interrupts robustly, without mentioning - concrete exception constructors in user code. Handled interrupts need to be - re-raised promptly! + \<^descr> \<^ML>\Fail\~\msg\ represents general program failures, but not user errors. + + \<^descr> \<^ML>\Exn.is_interrupt\ identifies interrupts, without mentioning + concrete exception constructors in user code. Since \<^ML_text>\handle\ with + catch-all patterns is rejected, it cannot handle interrupts at all. In the + rare situations where this is really required, use \<^ML>\Exn.capture\ and + \<^ML>\Exn.release\ (\secref{sec:managed-eval}). \<^descr> \<^ML>\Exn.reraise\~\exn\ raises exception \exn\ while preserving its implicit position information (if possible, depending on the ML platform). @@ -1201,9 +1207,41 @@ (@@{ML_antiquotation try} | @@{ML_antiquotation can}) embedded \ - \<^descr> \@{try}\ and \{can}\ are similar to the corresponding functions, but the - argument is taken directly as ML expression: functional abstraction and - application is done implicitly. + + \<^descr> \@{try}\ and \{can}\ take embedded ML source as arguments, and modify the + evaluation analogously to the combinators \<^ML>\try\ and \<^ML>\can\ above, + but with special treatment of the interrupt state of the underlying ML + thread. There are also variants to support \<^verbatim>\try_catch\ and \<^verbatim>\try_finally\ + blocks similar to Scala. + + The substructure of the embedded argument supports the following syntax + variants: + + \<^rail>\ + @{syntax_def try_catch}: @{syntax expr} @'catch' @{syntax handler}; + @{syntax_def try_finally}: @{syntax expr} @'finally' @{syntax cleanup}; + @{syntax_def try}: @{syntax expr}; + @{syntax_def can}: @{syntax expr} + \ + + The @{syntax handler} of \<^verbatim>\try_catch\ follows the syntax of \<^ML_text>\fn\ + patterns, so it is similar to \<^ML_text>\handle\: the key difference is + that interrupts are always passed-through via \<^ML>\Exn.reraise\. + + The @{syntax cleanup} expression of \<^verbatim>\try_finally\ is always invoked, + regardless of the overall exception result, and afterwards exceptions are + passed-through via \<^ML>\Exn.reraise\. + + Both the @{syntax handler} and @{syntax cleanup} are evaluated with further + interrupts disabled! These expressions should terminate promptly; timeouts + don't work here. + + \<^medskip> + Implementation details can be seen in \<^ML>\Isabelle_Thread.try_catch\, + \<^ML>\Isabelle_Thread.try_finally\, \<^ML>\Isabelle_Thread.try\, and + \<^ML>\Isabelle_Thread.can\, respectively. The ML antiquotations add + functional abstractions as required for these ``special forms'' of + Isabelle/ML. \<^descr> \@{assert}\ inlines a function \<^ML_type>\bool -> unit\ that raises \<^ML>\Fail\ if the argument is \<^ML>\false\. Due to inlining the source @@ -1214,16 +1252,21 @@ \ text %mlex \ - We define a total version of division: any failures are swept under the + + We define total versions of division: any failures are swept under the carpet and mapped to a default value. Thus division-by-zero becomes 0, but - there could be other exceptions like overflow that produce the same result - (for unbounded integers this does not happen). + there could be other exceptions like overflow that produce the same result. + + For unbounded integers such side-errors do not happen, but it might still be + better to be explicit about exception patterns (second version below). \ ML \ - fun div_total x y = \<^try>\x div y\ |> the_default 0; - - \<^assert> (div_total 1 0 = 0); + fun div_total1 x y = \<^try>\x div y catch _ => 0\; + fun div_total2 x y = \<^try>\x div y catch Div => 0\; + + \<^assert> (div_total1 1 0 = 0); + \<^assert> (div_total2 1 0 = 0); \ text \ @@ -1910,7 +1953,7 @@ \ -section \Managed evaluation\ +section \Managed evaluation \label{sec:managed-eval}\ text \ Execution of Standard ML follows the model of strict functional evaluation