由于懒惰的评价,我会养成好/坏的习惯吗?

时间:2010-09-23 16:59:41

标签: haskell functional-programming

我希望用Haskell或F#学习函数式编程。

是否有任何编程习惯(好的或坏的)可能形成Haskell的懒惰评估?为了理解函数式编程,我喜欢Haskell函数式编程纯度的概念。我只是有点担心两件事:

  1. 我可能会将基于惰性评估的功能误解为“功能范例”的一部分。
  2. 我可能会在懒惰的世界中发展思维模式,但不能在正常的秩序/热切的评估世界中发挥作用。

7 个答案:

答案 0 :(得分:21)

当使用不使用严格语言的懒惰语言进行编程时,您会遇到一些习惯。其中一些对于Haskell程序员来说似乎很自然,他们并不认为它们是懒惰的评估。我脑子里有几个例子:

f x y = if x > y then .. a .. b .. else c
  where
    a = expensive
    b = expensive 
    c = expensive

这里我们在where子句中定义了一堆子表达式,完全忽略了它们中的哪一个将被评估。无关紧要:编译器将确保在运行时不执行不必要的工作。非严格语义意味着编译器能够执行此操作。每当我用严格的语言写作时,我都会经常这么做。

另一个想到的例子是“编号”:

pairs = zip xs [1..]

这里我们只想将列表中的每个元素与其索引相关联,并使用无限列表[1..]进行压缩是在Haskell中执行此操作的自然方式。如果没有无限的名单,你怎么写?好吧,折叠不太可读

pairs = foldr (\x xs -> \n -> (x,n) : xs (n+1)) (const []) xs 1

或者你可以使用显式递归来编写它(太冗长,不会融合)。还有其他几种编写方法,其中没有一种方法像zip一样简单明了。

我相信还有更多。当你习惯时,懒惰是非常有用的。

答案 1 :(得分:11)

您肯定会了解评估策略。对于特定类型的编程问题,非严格的评估策略可能非常强大,一旦您接触到它们,您可能会因为在某些语言环境中无法使用它们而感到沮丧。

  

我可能会开发在懒惰世界中工作的思维模式,但不能在正常的顺序/热切的评估世界中工作。

右。你将成为一个更加圆润的程序员。提供“延迟”机制的抽象现在相当普遍,所以你是一个更糟糕的程序员,不知道它们。

答案 2 :(得分:6)

  
      
  1. 我可能会将基于惰性评估的功能误解为“功能范例”的一部分。
  2.   

懒惰评估功能范例的重要组成部分。这不是一个要求 - 您可以通过热切评估进行功能编程 - 但它是一种自然适合函数式编程的工具。

您会看到人们在不使其成为默认语言的语言中显式实现/调用它(特别是以延迟序列的形式);虽然将命令与命令式代码混合在一起需要谨慎,但纯粹的功能代码可以安全地使用懒惰。由于懒惰使许多结构更清洁,更自然,所以非常合适!

(免责声明:没有Haskell或F#体验)

答案 3 :(得分:1)

扩展Beni的答案:如果我们忽视效率方面的操作方面(并且暂时坚持纯粹的功能性世界),热切评估下的每个终止表达也会在非严格评估下终止,并且两者(他们的外延)重合。

这就是说,懒惰的评价比急切的评价更具表现力。通过允许您编写更正确和有用的表达式,它可以扩展您的“词汇量”和在功能上进行思考的能力。

以下是一个原因示例: 一种语言可以是默认的懒惰但具有可选的渴望,或者默认情况下具有可选的懒惰,但实际上已经显示(例如,参见Okasaki),某些纯功能数据结构只能达到某些性能顺序如果使用可选择或默认提供惰性的语言实现。

现在当你想要担心效率时,差异确实很重要,有时候你会想要严格,有时你也不会。

但是担心严格是一件好事,因为通常最干净的事情(而不仅仅是一种懒惰的语言)是使用懒惰和热切评价的深思熟虑的组合,并沿着这些思路思考无论你将来使用哪种语言都将是一件好事。

编辑:受Simon的帖子启发,还有一点:许多问题最自然地被认为是无限结构的遍历,而不是基本的递归或迭代。 (虽然这样的遍历本身通常会涉及某种递归调用。)即使对于有限结构,通常你只想探索一个可能很大的树的一小部分。一般来说,非严格的评估允许您停止混淆处理器实际上困扰的操作问题,以及表示您正在使用的实际结构的最自然方式的语义问题。

答案 4 :(得分:0)

我希望有坏习惯。

我看到我的一位同事尝试在我们的.NET项目中使用(手工编码)延迟评估。不幸的是,懒惰评估的结果隐藏了在主要执行开始之前尝试远程调用的错误,因此在try / catch之外处理“嘿我无法连接到互联网”的情况。

基本上,某种东西的方式隐藏了一个事实,即一个非常昂贵的东西隐藏在一个属性读取后面,因此在类型初始化器中做一个好主意。

答案 5 :(得分:0)

最近,我发现自己在Python中进行Haskell风格的编程。我接管了一个单片函数,它提取/计算/生成了值,并将它们放在文件接收器中,只需一步。

我认为这对理解,重用和测试都不利。我的计划是将价值创造和价值处理分开。在Haskell中,我会在纯函数中生成这些计算值的(惰性)列表,并且会在另一个(副作用方位)函数中进行后处理。

知道Python中的非惰性列表可能很昂贵,如果它们往往变大,我想到了下一个密切的Python解决方案。对我而言,使用生成器进行价值生成步骤。

由于我的懒惰(双关语意)心态,Python代码变得更好。

答案 6 :(得分:-2)

好吧,试着想一下如果懒惰地评估会有效的东西,如果急切地评价那就不会。这些中最常见的类别是用于隐藏“副作用”的惰性逻辑运算符评估。我会用C#-ish语言来解释,但函数式语言会有类似的类比。

采用简单的C#lambda:

(a,b) => a==0 || ++b < 20

在惰性评估语言中,如果a == 0,则表达式++ b&lt; 20未被计算(因为整个表达式的计算结果均为真),这意味着b不会递增。在命令式和函数式语言中,这种行为(以及AND运算符的类似行为)可用于“隐藏”包含不应执行的副作用的逻辑:

(a,b) => a==0 && save(b)
在这种情况下,“a”可能是验证错误的数量。如果存在验证错误,则前半部分失败,后半部分未评估。如果没有验证错误,则评估后半部分(其中包括尝试保存b的副作用)并返回结果(显然为真或假)以进行评估。如果任何一方评估为false,则lambda返回false,表示b未成功保存。如果这是“急切地”评估,我们会尝试保存,无论“a”的值是多少,如果非零“a”表明我们不应该这样做,那么这可能会很糟糕。

功能语言中的副作用通常被认为是禁忌。但是,很少有非平凡的程序不需要至少一个副作用;通常没有其他方法可以使功能算法与非功能代码集成,或与数据存储,显示,网络通道等外围设备集成。