You can rewrite this use of exceptions into using the 'a option
type instead. The original function:
exception Change;
fun change _ 0 = []
| change [] _ = raise Change
| change (coin::coins) amt =
if coin > amt
then change coins amt
else coin :: change (coin::coins) (amt-coin)
handle Change => change coins amt;
In the modified function below, instead of the exception bubbling up, it becomes a NONE
. One thing that becomes slightly more apparent here is that coin
only occurs in one of the two cases (where in the code above it always occurs but is reverted in case of backtracking).
fun change' _ 0 = SOME []
| change' [] _ = NONE
| change' (coin::coins) amt =
if coin > amt
then change' coins amt
else case change' (coin::coins) (amt-coin) of
SOME result => SOME (coin :: result)
| NONE => change' coins amt
Another way to demonstrate what happens is by drawing a call tree. This does not gather the result as Andreas Rossberg's evaluation by hand, but it does show that only the times change
is taking an else
-branch is there a possibility to backtrack, and if a backtrack occurs (i.e. NONE
is returned or an exception is thrown), don't include coin
in the result.
(original call ->) change [2,5] 7
\ (else)
`-change [2,5] 5
/ \ (else)
___________________/ `-change [2,5] 3
/ / \ (else)
/ / `-change [2,5] 1
`-change [5] 5 / \ (then)
\ (else) / `-change [5] 1
`-change [] 0 / \ (then)
\ / `-change [] 1
`-SOME [] `-change [5] 3 \ (base)
\ (then) `-NONE
`-change [] 3
\
`-NONE