Haskell 为什么惰性评估有用?
我一直在想为什么懒惰的评估是有用的。我还没有让任何人以有意义的方式向我解释;大多数情况下,它最终会变成“相信我” 注:我不是指备忘录 考虑一下:Haskell 为什么惰性评估有用?,haskell,functional-programming,lazy-evaluation,Haskell,Functional Programming,Lazy Evaluation,我一直在想为什么懒惰的评估是有用的。我还没有让任何人以有意义的方式向我解释;大多数情况下,它最终会变成“相信我” 注:我不是指备忘录 考虑一下: if (conditionOne && conditionTwo) { doSomething(); } 仅当conditionOne为true且conditionTwo为true时,才会执行doSomething()方法。 如果conditionOne为false,为什么需要计算conditionTwo的结果?在这种情况下,条件
if (conditionOne && conditionTwo) {
doSomething();
}
仅当conditionOne为true且conditionTwo为true时,才会执行doSomething()方法。
如果conditionOne为false,为什么需要计算conditionTwo的结果?在这种情况下,条件二的评估将是浪费时间,特别是如果您的条件是某个方法过程的结果
这是惰性评估兴趣的一个例子…主要是因为它可以更有效——如果不使用值,则不需要计算它们。例如,我可以将三个值传递到一个函数中,但根据条件表达式的顺序,实际上只能使用一个子集。在像C这样的语言中,所有三个值都会被计算出来;但在Haskell中,只计算必要的值
它还允许一些很酷的东西,比如无限列表。在像C这样的语言中,我不能有一个无限列表,但在Haskell中,这没有问题。无限列表在数学的某些领域中使用得相当频繁,因此能够对其进行操作是非常有用的。当您打开计算机,Windows禁止在Windows资源管理器中打开硬盘上的每个目录,也禁止启动计算机上安装的每个程序时,除非您指出需要某个目录或某个程序,否则这就是“惰性”评估 “惰性”评估是在需要时执行操作。当它是编程语言或库的一个特性时,它是有用的,因为单独实现惰性计算通常比预先计算所有内容都要困难。如果“惰性计算”是指combound booleans中的like,如
if (ConditionA && ConditionB) ...
那么答案很简单,程序消耗的CPU周期越少,运行速度就越快。。。如果一块处理指令对程序的结果没有影响,那么执行它们是不必要的(因此是浪费时间的)
如果是otoh,您指的是我所知道的“懒惰的初始值设定者”,如:
好的,这种技术允许使用类的客户端代码避免为主管数据记录调用数据库,除非使用Employee对象的客户端需要访问主管的数据。。。这使得实例化员工的过程更快,但当您需要主管时,对主管属性的第一次调用将触发数据库调用,数据将被提取并可用 此代码段显示了惰性计算和非惰性计算之间的区别。当然,这个斐波那契函数本身可以进行优化,并使用惰性求值而不是递归,但这会破坏示例 让我们假设我们可能必须使用前20个数字来表示某件事,而不是使用惰性计算,所有的20个数字都必须预先生成,但是,使用惰性计算,它们将仅在需要时生成。因此,您只需在需要时支付计算价格 样本输出 Not lazy generation: 0.023373 Lazy generation: 0.000009 Not lazy output: 0.000921 Lazy output: 0.024205 不懒惰的一代:0.023373 懒惰一代:0.000009 非惰性输出:0.000921 延迟输出:0.024205
导入时间
def now():返回时间
def fibonacci(n):#fibonacci的递归(非惰性)
如果n<2:
返回n
其他:
返回斐波那契(n-1)+斐波那契(n-2)
before1=现在()
notlazy=[fibonacci(x)表示范围(20)内的x]
after1=现在()
before2=现在()
lazy=(范围(20)内x的斐波那契(x))
after2=现在()
before3=现在()
因为我不懒惰:
打印i
after3=现在()
before4=现在()
因为我懒惰:
打印i
after4=现在()
打印“非延迟生成:%f”%(在1之后-在1之前)
打印“延迟生成:%f”%(2之后-2之前)
打印“非惰性输出:%f”%(3之后-3之前)
打印“延迟输出:%f”%(4之后-4之前)
我发现惰性评估在很多方面都很有用
首先,所有现有的惰性语言都是纯的,因为很难对惰性语言的副作用进行推理
纯语言允许您使用等式推理对函数定义进行推理
foo x = x + 3
不幸的是,在非惰性设置中,比惰性设置中有更多的语句无法返回,因此这在像ML这样的语言中不太有用。但在惰性语言中,您可以安全地对等式进行推理
其次,像Haskell这样的惰性语言不需要ML中的“值限制”之类的东西。这将导致语法的大混乱。类似ML的语言需要使用var或fun等关键字。在哈斯凯尔,这些东西归结为一个概念
第三,懒惰可以让您编写非常实用的代码,这些代码可以被理解为片段。在Haskell中,通常编写函数体,如:
foo x y = if condition1
then some (complicated set of combinators) (involving bigscaryexpression)
else if condition2
then bigscaryexpression
else Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
这使您能够通过理解函数体“自上而下”地工作。类似ML的语言迫使您使用严格评估的let
。因此,您不敢将let子句“提升”到函数的主体,因为如果它很昂贵(或有副作用),您不希望总是对它进行评估。Haskell可以明确地将细节“推送”到where子句,因为它知道该子句的内容只会根据需要进行评估
在实践中,我们倾向于使用防护装置并进一步折叠:
foo x y
| condition1 = some (complicated set of combinators) (involving bigscaryexpression)
| condition2 = bigscaryexpression
| otherwise = Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
第四,懒惰有时会为某些算法提供更优雅的表达方式。Haskell中的惰性“快速排序”是一个单行程序,它的好处是,如果只查看前几个项目,您只需支付与选择这些项目的成本成比例的成本。没有什么可以阻止您严格执行此操作,但是您可能每次都必须重新编码算法以获得相同的渐近性能
第五,懒惰允许你定义自己
foo x y = if condition1
then some (complicated set of combinators) (involving bigscaryexpression)
else if condition2
then bigscaryexpression
else Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
foo x y
| condition1 = some (complicated set of combinators) (involving bigscaryexpression)
| condition2 = bigscaryexpression
| otherwise = Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
if' True x y = x
if' False x y = y
bool Function(void) {
if (!SubFunction1())
return false;
if (!SubFunction2())
return false;
if (!SubFunction3())
return false;
(etc)
return true;
}
bool Function(void) {
if (!SubFunction1() || !SubFunction2() || !SubFunction3() || (etc) )
return false;
return true;
}
quickSort [] = []
quickSort (x:xs) = quickSort (filter (< x) xs) ++ [x] ++ quickSort (filter (>= x) xs)
minimum ls = head (quickSort ls)
square x = x * x
square (square (square 2))
> square (square (2 * 2))
> square (square 4)
> square (4 * 4)
> square 16
> 16 * 16
> 256
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * (square (square 2))
> ((2 * 2) * (square 2)) * (square (square 2))
> (4 * (square 2)) * (square (square 2))
> (4 * (2 * 2)) * (square (square 2))
> (4 * 4) * (square (square 2))
> 16 * (square (square 2))
> ...
> 256
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * ((square 2) * (square 2))
> ((2 * 2) * (2 * 2)) * ((2 * 2) * (2 * 2))
> (4 * 4) * (4 * 4)
> 16 * 16
> 256
square (square (square 2))
||
\/
*
/ \
\ /
square (square 2)
||
\/
*
/ \
\ /
*
/ \
\ /
square 2
||
\/
*
/ \
\ /
*
/ \
\ /
*
/ \
\ /
2
if( obj != null && obj.Value == correctValue )
{
// do smth
}
type 'a stack =
| EmptyStack
| StackNode of 'a * 'a stack
let rec append x y =
match x with
| EmptyStack -> y
| StackNode(hd, tl) -> StackNode(hd, append tl y)
type 'a lazyStack =
| StackNode of Lazy<'a * 'a lazyStack>
| EmptyStack
let rec append x y =
match x with
| StackNode(item) -> Node(lazy(let hd, tl = item.Force(); hd, append tl y))
| Empty -> y
fix f = f (fix f)
f (f (f ....
fact = fix $ \f n -> if n == 0 then 1 else n * f (n-1)
def fix[A](f: A => A): A = f(fix(f))
val fact = fix[Int=>Int] { f => n =>
if (n == 0) 1
else n*f(n-1)
}
def fix[A](f: (=>A) => A): A = f(fix(f))
def fact1(f: =>Int=>Int) = (n: Int) =>
if (n == 0) 1
else n*f(n-1)
val fact = fix(fact1)
largestDivisible :: (Integral a) => a
largestDivisible = head (filter p [100000,99999..])
where p x = x `mod` 3829 == 0