互斥条款的顺序在函数或匹配表达式中是否重要

时间:2013-09-12 17:24:38

标签: f# functional-programming

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"

我更喜欢风格上的第一个,因为模式按大小的顺序列出/基本情况是第一个。但是第二个有什么优势吗?特别是,第二个更有效,因为在正常评估过程中永远不会检查例外情况吗?

2 个答案:

答案 0 :(得分:6)

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

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

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

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

答案 1 :(得分:5)

我通常首先使用递归函数的完成情况。这通常是0案例或1案例。接下来我把recurses的案例(假设只有一个),然后是任何特殊情况。

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

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