The General structure contains miscellaneous definitions.
The exnName and exnMessage are important for dealing with uncaught exceptions in your programs. You should always have some exception handlers around a top-level function looking something like this.
val success = OS.Process.success
val failure = OS.Process.failure
fun run args =
let
...
in
process args;
success
end
handle
IO.Io {name, function, cause} =>
(
toErr(concat["IO Error ", name,
" ", function,
" ", exnMessage cause, "\n"]);
failure
)
| Fatal => (toErr "Aborting\n"; failure)
| InternalError msg =>
(
toErr(concat["Internal error, ", msg, "\n"]);
failure
)
(* misc exception *)
| x =>
(
toErr(concat["Uncaught exception: ", exnMessage x,"\n"]);
failure
) |
I usually have a Fatal exception to abort a program upon a fatal error. Each site that detects the error produces an error message and raises Fatal to abort the program. I also have an InternalError exception to report all places that should be unreachabled. Each site that raises InternalError must include a message that uniquely identifiers the site for debugging. The last handler above catches all other exceptions and describes them.
The functions ! and := for dereferencing and assignment of imperative variables are defined here. Assignment is made infix with a precedence in the top-level environment. Dereference is an ordinary prefix function and so you will normally need to use parentheses around it. For example f !x is interpreted as a call to the function f with two arguments, ! and x when you probably meant f (!x).
The function o composes two functions together. It is sometimes useful when passing a combination of functions as an argument. For example a function map (lookup o uppercase) could be applied to a list of strings to replace each one by converting it to uppercase and then passing it through a lookup function.
The before function looks a bit odd. You can use it to attach tracing messages to expressions. For example
let
....
in
print "starting f\n";
f x before print "done f\n"
end |