当 m 和 n 都为零时,您的函数不会抛出。 (我的版本也没有使用模式匹配。)如果 m 和 n 都为零,您可能想要编写一个额外的 if-then-else 并抛出,例如喜欢,
fun power (m, 0) = 1
| power (m, n) = if n < 0 then raise Domain else m * power (m, n-1)
这个函数的一个坏处是它会检查n < 0是否在每次递归调用它自己时,真的,你知道如果它第一次是肯定的并且基本情况会在0处捕获它,它在以后的任何阶段都不会是负面的。一个优雅的解决方案是将函数的递归部分包装在执行这些检查一次的非递归函数中,例如喜欢,
fun power (0, 0) = raise Domain
| power (m, n) = if n < 0 then raise Domain else naive_power (m, n)
其中naive_power 是上面假定其输入有效的函数。
这个函数的另一个坏处是它不是尾递归的,虽然它很容易做到。也就是说,对power (m, 5) 的调用将按如下方式计算:
power (2, 5) ~> 2 * (power (m, 4))
~> 2 * (2 * (power (m, 3)))
~> 2 * (2 * (2 * (power (m, 2))))
~> 2 * (2 * (2 * (2 * (power (m, 1)))))
~> 2 * (2 * (2 * (2 * (2 * power (m, 0)))))
~> 2 * (2 * (2 * (2 * (2 * 1))))
~> 2 * (2 * (2 * (2 * 2)))
~> 2 * (2 * (2 * 4))
~> 2 * (2 * 8)
~> 2 * 16
~> 32
意思是很多函数调用等待下一个函数调用解决,然后自己才能解决。尾递归版本可能会使用一个额外的参数来存储临时结果并在最后返回它:
fun power (0, 0) = raise Domain
| power (M, N) =
let fun power_helper (m, 0, result) = result
| power_helper (m, n, tmp) = power_helper (m, n-1, tmp * m)
in if N < 0 then raise Domain else power_helper (M, N, 1) end
将辅助函数嵌入到其他函数中可能很有用,因为您需要执行一次特定检查并将算法的主要递归部分解析到另一个函数中,或者因为您希望向递归函数添加更多参数不破坏类型签名。 (power_helper 需要三个参数,因此尾递归版本在没有被包装的情况下不会成为编写具有两个参数来计算 mⁿ 的函数的有效解决方案。
评估power (2, 5) 假设它的尾递归实现可能如下所示:
power (2, 5) ~> power_helper (2, 5, 1)
~> power_helper (2, 4, 2)
~> power_helper (2, 3, 4)
~> power_helper (2, 2, 8)
~> power_helper (2, 1, 16)
~> power_helper (2, 0, 32)
~> 32