我对编写高效功能程序的正确方法有疑问。假设我给出了一个正整数的列表,我想找到最小元素(如果为空则只需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中优化它的正确方法是什么?
其他问题:好的,如果我理解,如果陈述总是很懒惰的话。但是下面呢,例如:我再次在int列表上有一个函数,它首先检查第i个元素是否为零,即
f s = if s(i)==0 then 0 else g s
这里输入序列s出现在if语句的两个子句中,但显然对于有效的计算,你只想在第一种情况下评估s(i)。在这里,OCaml总会评估所有s,即使第一种情况成功吗?
答案 0 :(得分:2)
if
个表达式不遵循严格的评估规则。
喜欢||和&&,懒得评价。
请看这个链接:if expressions
答案 1 :(得分:1)
在严格评估的语言中,将评估整个列表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
,而在严格的语言中,相同的表达式将永远循环。
答案 2 :(得分:1)
首先,空列表的最小值是未定义的,而不是0
。这是有道理的,否则minList [1,2,3]
将是0
,这显然不正确。这就是ghci
所说的:
Prelude> minimum []
*** Exception: Prelude.minimum: empty list
因此,您的功能应写为:
let minList (x::t) = min x (minList t)
这个定义存在一些问题:
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
。
懒惰的评价在这里没有发挥作用。
答案 3 :(得分:0)
几乎所有现代编程语言:
对于expr1 && expr2
,如果expr1已经为假,则不会评估expr2。
对于expr1 || expr2
,如果expr1已经为真,则不会对expr2进行评估。
OCaml也是这样做的。