我在我的SML手册中看到了以下功能,它可以计算特定变化所需的特定种类的硬币数量。
例如change [5,2] 16 =[5,5,2,2,2]
因为有2个5个硬币和3个2个硬币,所以有16个。
以下代码是一种回溯方法:
exception Change;
fun change _ 0 = nil|
change nil _ = raise Change|
change (coin::coins)=
if coin>amt then change coins amt
else (coin:: change (coin::coins) (amt-coin))
handle Change=> change coins amt;
它有效,但我不明白究竟是怎么回事。 我知道回溯是什么,我只是不明白这个特殊的功能。
到目前为止我所理解的:如果amt为0,则表示我们的更改已计算完毕,并且没有任何内容可以列入最终列表。
如果我们的'硬币列表'中没有更多硬币,我们需要返回一步。 这是我迷路的地方:如何提出异常有助于我们回去? 在我看来,处理程序试图调用change函数,但是“coins”参数不应该是nil吗?因此进入无限循环?为什么会“回去”;
最后一句对我来说非常明显:如果硬币值大于要改变的金额,我们使用剩余的硬币来构建变化。如果它小于剩余的数量,我们将其纳入结果列表。
答案 0 :(得分:4)
最好通过写一个简单的例子来说明评估的进展情况。在每一步中,我只是在相应的右侧替换change
的呼叫(为了更加清晰,我添加了额外的括号):
change [3, 2] 4
= if 3 > 4 then ... else ((3 :: change [3, 2] (4 - 3)) handle Change => change [2] 4)
= (3 :: change [3, 2] 1) handle Change => change [2] 4
= (3 :: (if 3 > 1 then change [2] 1 else ...)) handle Change => change [2] 4
= (3 :: change [2] 1) handle Change => change [2] 4
= (3 :: (if 2 > 1 then change [] 1 else ...)) handle Change => change [2] 4
= (3 :: (raise Change)) handle Change => change [2] 4
此时已引发异常。它会冒泡到当前的处理程序,以便评估按如下方式进行:
= change [2] 4
= if 2 > 4 then ... else ((2 :: change [2] (4 - 2)) handle Change => change [2] 4)
= (2 :: change [2] 2) handle Change => change [2] 4
= (2 :: (if 2 > 2 then ... else ((2 :: change [2] (2 - 2)) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: ((2 :: change [2] 0) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: ((2 :: []) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: (2 :: [])) handle Change => change [2] 4
= 2 :: 2 :: []
此处不再有失败,所以我们成功终止。
简而言之,每个处理程序都是一个回溯点。在每次失败(即,加注)时,您将进入最里面的处理程序,这是最后一个回溯点。设置每个处理程序本身,使其包含相应的调用来尝试。
答案 1 :(得分:3)
您可以使用'a option
类型重写此异常使用。原来的功能:
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;
在下面的修改函数中,它不是异常冒泡,而是NONE
。这里稍微明显的一件事是coin
只出现在两种情况中的一种情况下(在上面的代码中总是会发生,但在回溯的情况下会被恢复)。
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
另一种展示所发生情况的方法是绘制一个调用树。这并没有像安德烈亚斯·罗斯伯格手工评估那样收集结果,但它确实表明只有时间change
正在采用else
- 分支才有可能回溯,并且如果发生回溯(即返回NONE
或抛出异常),不要在结果中包含coin
。
(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
答案 2 :(得分:0)
来源:https://www.cs.cmu.edu/~rwh/introsml/core/exceptions.htm
<块引用>表达式 exp handle
match 是一个异常处理程序。这是
通过尝试评估 exp 来评估。如果它返回一个值,那么
那就是整个表达式的值;处理程序没有任何作用
在这种情况下。但是,如果 exp 引发异常 exn,则
异常值与匹配的子句匹配(正好
就像将子句函数应用于参数一样)到
决定如何进行。如果子句的模式匹配
异常 exn,然后评估继续使用的表达式部分
那个条款。如果没有模式匹配,异常 exn 被 重新引发 所以
外部异常处理程序可能会调度它。如果没有处理程序
处理异常,然后未捕获的异常作为
评估的最终结果。也就是说,计算被中止
未捕获的异常exn。
在更多操作方面,对 exp handle
match 的评估通过
安装由 match 确定的异常处理程序,然后评估
经验。异常处理程序的先前绑定被保留,因此
一旦不再需要给定的处理程序,它就可以恢复。
引发异常包括将 exn 类型的值传递给
当前异常处理程序。将异常传递给处理程序
卸载该处理程序,并重新安装以前活动的
处理程序。这确保如果处理程序本身引发异常,
或未能处理给定的异常,则异常是
在评估 handle
之前传播到活动处理程序
表达。如果表达式没有引发异常,则
作为完成评估的一部分,恢复以前的处理程序
handle
表达式。