【问题标题】:Programming style in OCamlOCaml 中的编程风格
【发布时间】:2014-05-18 01:25:51
【问题描述】:

我有一个关于编写高效函数式程序的正确方法的问题。假设我得到一个正整数列表,我想找到最小元素(如果为空,则为 0)。然后一个通用的函数程序会看起来像

minList s =
    | [] -> undefined
    | [x] -> x
    | x :: t -> min x (minList t) 

在惰性语言中,可以通过添加一个额外的子句来提高效率,如果找到零则终止递归 - 这样 s 只计算到​​第一个零

minList s = 
    | [] -> undefined
    | [x] -> x
    | x :: t -> if x==0 then 0 else min x (minList t)

但是,我是否认为这种技巧在像 OCaml 这样的严格评估语言中不起作用,它会在运行 minList 之前评估整个 s?如果是这样,在 OCaml 中优化它的正确方法是什么?

附加问题:好的,如果我理解 if 语句总是懒惰的。但是以下情况呢,例如:我在 int 列表上有一个函数,它首先检查第 i 个元素是否为零,即

f s = if s(i)==0 then 0 else g s

这里的输入序列 s 出现在 if 语句的两个子句中,但显然为了高效计算,您只想在第一种情况下评估 s(i)。在这里,OCaml 是否总是评估所有的 s,即使第一个案例成功了?

【问题讨论】:

  • 绝对可以。如果布尔表达式为真,你永远不会评估 if 语句的假面——这会在任何语言中导致很多问题。

标签: haskell functional-programming ocaml


【解决方案1】:

if ocaml 中的表达式不遵循严格的评估规则。 喜欢 ||和 &&,它是懒惰地评估的。 见此链接:if expressions

【讨论】:

    【解决方案2】:

    在严格评估的语言中,将评估整个列表 s。不过,

    minList s = 
        | [] -> 0
        | x :: t -> if x==0 then 0 else min x (minList t)
    

    如果找到0,则不会扫描整个列表。

    if 构造具有“非严格”语义,因为它只会评估一个分支,而不是两个分支。这在严格和非严格语言中都成立。

    实际的区别是在调用“用户定义的 if”时,例如(使用 Haskell 语法):

    myIf :: Bool -> a -> a
    myIf b x y = if b then x else y
    

    在非严格语言中,调用myIf True 3 (nonTerminatingFunction ()) 将产生3,而在严格语言中,相同的表达式将永远循环。

    【讨论】:

      【解决方案3】:

      首先,一个空列表的最小值是未定义的,而不是0。这是有道理的,否则minList [1,2,3] 将是0,这显然不是真的。这就是ghci 不得不说的:

      Prelude> minimum []
      *** Exception: Prelude.minimum: empty list
      

      因此你的函数应该写成:

      let minList (x::t) = min x (minList t)
      

      这个定义有一些问题:

      1. 仍然会报错,因为空列表没有模式匹配。
      2. 它不是尾递归的。
      3. 如果头部是0,它不会停止。

      所以这里有一个更好的解决方案:

      let minimum x xs = match x,xs
        | 0,xs        -> 0
        | x,[]        -> x
        | x,(y :: ys) -> minimum (min x y) ys
      
      let minList = function
        | [] -> raise Failure "No minimum of empty list"
        | x::t -> minimum x t
      

      这样写的好处是minimum是尾递归的。因此它不会增加堆栈大小。此外,如果头部是0,它将立即返回0

      懒惰的评估在这里没有任何作用。

      【讨论】:

      • 感谢您的详细回复。是的,我原始代码中的愚蠢错误。不过,我的主要问题是惰性求值在“if”或模式匹配语句中是否有任何作用,我认为这已经在其他答案中得到了澄清。
      【解决方案4】:

      在几乎所有现代编程语言中:

      对于expr1 && expr2,如果 expr1 已经为 false,则不会计算 expr2。

      对于expr1 || expr2,如果 expr1 已经为真,则不会计算 expr2。

      OCaml 也这样做。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-06-16
        • 2011-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多