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 |