Haskell,Lisp和冗长程度[关闭]


104

对于那些同时具有Haskell和Lisp风格的经验的人,我很好奇在Haskell vs. Lisp中编写代码是多么“令人愉悦”(使用恐怖的术语)。

背景:我现在正在学习Haskell,之前曾与Scheme和CL一起工作过(并且对Clojure有所涉猎)。传统上,您可以认为我是动态语言的迷,因为它们提供了简洁性和快速性。我很快就爱上了Lisp宏,因为它为我提供了另一种避免冗长和模样的方法。

我发现Haskell 非常有趣,因为它向我介绍了我不知道的编码方式。它肯定有某些方面似乎有助于实现敏捷性,例如易于编写部分函数。但是,我有点担心丢失Lisp宏(我想我丢失了它们;说实话我可能还没有了解它们?)和静态类型系统。

在这两个世界上都做过相当不错的编码的人是否会评论一下体验如何不同,您更喜欢哪一种,以及所说的偏好是视情况而定的?

Answers:


69

简短答案:

  • 您几乎可以对宏执行的任何操作都可以对高阶函数执行(我包括单子,箭头等),但是可能需要更多的思考(但这只是第一次,这很有趣,您将成为更好的程序员),和
  • 静态系统足够笼统,以至于它永远不会妨碍您,并且令人惊讶的是它实际上“有助于实现敏捷性”(如您所说),因为在您的程序编译时,您几乎可以肯定是正确的,因此这种确定性可以让您尝试排除您可能不敢尝试的东西-尽管与Lisp不同,但是编程具有“动态”的感觉。

[注意:有一个“ Template Haskell ”,可以像在Lisp中一样编写宏,但严格来说,您永远不需要它。


15
唐·斯图尔特(Don Stewart)引用康纳·麦克布赖德(Conor McBride)的话说:“我喜欢将类型视为扭曲我们的引力,以便我们(编写正确程序)需要走的方向变得“下坡”。 类型系统使编写正确的程序变得异常容易……请参阅此文章及其转发。
ShreevatsaR

3
高阶函数不能替换宏,实际上,CL由于某种原因而具有两者。CL中宏的真正力量在于,它们使开发人员能够引入新的语言功能,从而帮助更好地表达问题的解决方案,而不必像Ha​​skell或Java中那样等待该语言的新版本。例如,如果Haskell具有此功能,那么开发人员可以随时将其作为宏来实现,而无需Haskell作者编写GHC扩展。
mljrg

@mljrg您有具体的例子吗?请参阅下面有关Hibou57答案的评论,其中有一个被举报的例子令人怀疑。我很想知道您的意思(例如,带或不带宏的Haskell代码)。
ShreevatsaR 2015年

4
从Haskell取出咖喱。您能用Haskell剩下的内容实现它吗?另一个例子:假设Haskell不支持模式匹配,您是否可以自己添加它而不需要GHC的开发人员来支持它?在CL中,您可以使用宏随意扩展语言。我想这就是为什么CL语言自90年代以来就没有改变的原因,而Haskell似乎在GHC扩展中无止境地变化着。
mljrg 2015年

64

首先,不必担心会丢失诸如动态类型之类的特定功能。正如您对Common Lisp(一种设计出色的语言)所熟悉的那样,我想您知道一种语言无法还原为其功能集。这都是一个连贯的整体,不是吗?

在这方面,Haskell与Common Lisp一样明亮。它的功能结合在一起,为您提供了一种使代码极短而优雅的编程方式。诸如monads和arrow之类的更精细(但同样难以理解和使用)的概念在某种程度上缓解了宏的缺失。静态类型系统增加了您的能力,而不是像大多数面向对象的语言那样妨碍您的工作。

另一方面,Haskell中的编程比Lisp的交互性要差得多,并且像Lisp这样的语言中存在的大量反射不符合Haskell所假设的静态世界。因此,您可以使用的工具集在两种语言之间是完全不同的,但是很难相互比较。

我个人通常更喜欢Lisp编程方式,因为我觉得它适合我更好的工作方式。但是,这并不意味着您也一定会这样做。


4
您能否详细说明一下“在Haskell中进行编程要少得多的交互性”。GHCi不能真正提供您需要的一切吗?
Johannes Gerer 2013年

3
@JohannesGerer:我还没有尝试过,但是据我所读,GHCi并不是正在运行的映像的外壳,您可以在其中运行时重新定义和扩展整个程序的任意部分。同样,Haskell语法使以编程方式在repl和编辑器之间复制程序片段变得更加困难。
Svante

13

Haskell中的元编程需求比Common Lisp中的少,因为可以围绕单子结构进行大量编程,并且所添加的语法使嵌入式DSL看起来不像树一样,但是ShreevatsaR提到的总有Template Haskell,甚至还有Liskell(Haskell语义+ Lisp语法)(如果您喜欢括号)。


1
Liskell 链接已死,但是现在有Hackett了
尼斯(Ness Ness)

10

关于宏,下面是讨论宏的页面:Hello Haskell,Goodbye Lisp。它解释了一种观点,其中Haskell不需要宏。它带有一个简短的比较示例。

需要使用LISP宏来避免同时评估两个参数的示例情况:

(defmacro doif (x y) `(if ,x ,y))

Haskell不需要像宏定义这样的东西就不会系统地评估两个参数的示例情况:

doif x y = if x then (Just y) else Nothing

还有


17
这是一个普遍的误解。是的,在Haskell中,惰性意味着您在想要避免对表达式的某些部分求值时不需要宏,但是这些只是所有宏使用中最琐碎的子集。Google在“ Perl之前的猪”上的演讲,演示了一个懒惰无法完成的宏。同样,如果您确实希望严格一点,那么您就不能将它作为函数来做-反映Scheme delay不能是函数的事实。
Eli Barzilay

8
@Eli Barzilay:我觉得这个例子没有说服力。这是幻灯片40的完整,简单的Haskell译文:pastebin.com/8rFYwTrE
Reid Barton 2010年

5
@Eli Barzilay:我完全不理解您的回应。accept (E)DSL。该accept函数类似于前几页中概述的宏,并且其定义与幻灯片40上Scheme中v的定义完全平行v。Haskell和Scheme函数使用相同的评估策略计算相同的事物。宏最多可以使您向优化程序公开程序的更多结构。您几乎不能以宏为例来说明这一点,其中宏以一种懒惰求值无法复制的方式提高了语言的表达能力。
里德·巴顿

5
@Eli Barzilay:在一个假设的懒惰方案中,您可以这样写:pastebin.com/TN3F8VVE我的一般主张是,该宏买的很少:语法略有不同,优化器的使用时间更短(但是一个“足够智能的编译器”)。作为交换,您陷入了低俗的语言中。如何定义匹配任何字母但不列出所有字母的自动机?另外,我不知道“在所有子列表中使用它”或“在具有其自身作用域的位置的必要使用”是什么意思。
Reid Barton

15
好我放弃 显然,您对DSL的定义是“宏的参数”,所以我的惰性方案示例不是DSL,尽管在语法上与原始语法同构(在此版本中automaton成为letrec:成为accept->变为无)。随你。
Reid Barton

9

我是一名Lisp Common程序员。

前一段时间尝试过Haskell时,我个人的底线是坚持使用CL。

原因:

  • 动态类型(请查看动态类型与静态类型— Pascal Costanza基于模式的分析
  • 可选参数和关键字参数
  • 带有宏的统一谐音列表语法
  • 前缀语法(无需记住优先规则)
  • 不纯,因此更适合快速原型制作
  • 带有元对象协议的强大对象系统
  • 成熟标准
  • 各种各样的编译器

当然,Haskell确实有其优点,并且以根本不同的方式来做某些事情,但是从长远来看,这对我来说是没有用的。


嘿,您碰巧有您链接到的Costanza论文的标题吗?看起来该文件已移动。
michiakig

2
请注意,haskell也支持前缀语法,但是我会说monad >> =使用它非常难看。我也不同意杂质是一种祝福:P
替代

2
我喜欢这个旁注:我们还没有收集经验数据,这个问题是否会在实际程序中引起严重的问题。
Jerome Baum

22
该论文中的所有示例(Pascal Costanza,动态与静态键入-基于模式的分析)均不适用于Haskell。它们都是特定于Java的(或更确切地说,特定于“面向对象的编程”),我看不到Haskell中出现的任何这些问题。同样,您的所有其他参数也是有争议的:您还可以说Haskell是“纯正的,因此更适合快速原型设计”,前缀语法不是强制性的,没有各种各样的编译器可以做不同的事情等等
ShreevatsaR 2011年

11
那篇论文的确与Haskell几乎完全无关。” dilbert = dogbert.hire(dilbert);“?我怀疑许多Haskell程序员甚至都可以在不抽搐的情况下阅读它。
大约

6

在Haskell中,您可以定义if函数,这在LISP中是不可能的。由于懒惰,这是可能的,这使得程序具有更多的模块化。约翰·休斯(John Hughes)撰写的这篇经典论文:“ FP为什么重要”解释了懒惰如何增强可组合性。


5
Scheme(两个主要的LISP方言之一)实际上确实具有惰性评估,尽管它不是Haskell中的默认值。
兰迪·沃特

7
(defmacro doif(xy)`(if,x,y))
约书亚脸颊

4
宏与函数并不相同-宏不能与高阶函数fold(例如)配合使用,而非严格函数则可以
迪洪·耶维斯

5

在Lisp中,使用Haskell繁琐(如果可能)的宏可以实现非常酷的事情。以“ memoize”宏为例(请参阅Peter Norvig的PAIP的第9章)。有了它,您可以定义一个函数,例如foo,然后简单地求值(记住'foo),该函数将foo的全局定义替换为已记忆的版本。在Haskell中使用高阶函数可以达到相同的效果吗?


3
不完全(AFAIK),但是您可以通过修改函数(假设它是递归的)以将函数作为参数(!)进行递归调用而做类似的事情,而不是简单地通过名称本身进行调用:haskell.org/haskellwiki/Memoization
j_random_hacker 2010年

6
您可以将foo添加到惰性数据结构中,该值一旦计算就将存储在其中。这实际上是相同的。
Theo Belaire

Haskell编译器默认情况下会记忆Haskell中的所有内容,并且可能在需要时将其内联。
aoeu256 '19

4

在我继续进行Haskell学习过程时,似乎可以帮助“替换”宏的一件事是定义自己的中缀运算符并自定义其优先级和关联性的能力。有点复杂,但有趣的系统!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.