5

functionmatch语句中的子句不相互排斥时,顺序显然很重要。但是,当子句互斥时,它们可以按任何顺序编写例如,要找到列表中的最小元素,以下在功能上是等效的:

let rec minElt =
    function
    | [] -> failwith "empty list"
    | [x0] -> x0
    | x0::xtl -> min x0 (minElt xtl)

let rec minElt =
    function
    | [x0] -> x0
    | x0::xtl -> min x0 (minElt xtl)
    | [] -> failwith "empty list"

我在风格上更喜欢第一个,因为这些图案是按尺寸递增的顺序列出的/基本情况排在第一位。但是,第二个有什么优势吗?特别是,第二个是否更有效,因为在正常评估过程中永远不会检查异常情况?

4

2 回答 2

6

我不认为有任何既定的惯用风格。我会首先专注于使代码可读和易于理解 - 我认为这取决于个人喜好,但我想你可以写:

  • 特殊情况优先(任何需要特殊处理,或处理特殊但有效的值)
  • 接下来是最常见的情况(典型路径,例如x::xs列表)
  • 例外情况(任何意味着无效输入的情况)

我想这就是我通常倾向于编写模式匹配的方式(因为这是我考虑可能情况的顺序)。

我不会太担心性能。我只是出于好奇测试了你的功能。我在长度为 1 到 100 的列表上调用了 1000 次(即 100000 次迭代),第一个大约为 895 毫秒,第二个为 878 毫秒,因此差异为 2%。听起来不像是对可读性很重要的东西(这是在 F# Interactive 中,所以差异可能更小)。

于 2013-09-12T18:37:32.810 回答
5

我通常将递归函数的完成情况放在首位。这通常是这种0情况或1情况。接下来我提出递归的情况(假设只有一个),然后是任何例外情况。

这背后的原因是我理解归纳推理的方式:即我理解基本情况(递归如何结束),然后是基本情况如何用于证明算法的正确性(递归如何结束)。我把例外情况放在最后,因为它们不会增加我对递归背后逻辑的理解。如果你从论证的中间开始,递归比你从结尾开始更难理解(并且由于递归没有明确定义的起点,你不能很好地从那里开始)。

这种推理很大程度上依赖于函数式编程的数学基础,所以如果这不是你处理函数式编程的方式,那么其他一些排序对你来说可能更有意义。

于 2013-09-12T18:23:16.520 回答