Haskell Lisp 和冗长

对于那些经历过 Haskell 和一些 Lisp 风格的人来说,我很好奇用哈斯克尔和 Lisp 编写代码是多么“愉快”(用一个可怕的术语)。

一些背景知识: 我现在正在学习 Haskell,之前曾经使用 Scheme 和 CL (并尝试了一下 Clojure)。传统上,您可以认为我是动态语言的粉丝,因为它们提供了简洁和快速。我很快就爱上了 Lisp 宏,因为它给了我另一种避免冗长和样板的方法。

我发现 Haskell难以置信很有趣,因为它向我介绍了一些我不知道存在的编码方法。它肯定有一些方面似乎有助于实现敏捷性,比如易于编写部分函数。然而,我有点担心失去 Lisp 宏(我假设我失去了它们; 说实话,我可能只是还没有了解它们?)和静态打字系统。

任何一个在两个世界都编写了大量代码的人,会介意评论一下这两种体验的不同之处吗? 你更喜欢哪种体验? 如果所说的偏好是情景性的?

29203 次浏览

First of all, don't worry about losing particular features like dynamic typing. As you're familiar with Common Lisp, a remarkably well-designed language, I assume you're aware that a language can't be reduced to its feature set. It's all about a coherent whole, isn't it?

In this regard, Haskell shines just as brightly as Common Lisp does. Its features combine to provide you with a way of programming that makes code extremely short and elegant. The lack of macros is mitigated somewhat by more elaborate (but, likewise, harder to understand and use) concepts like monads and arrows. The static type system adds to your power rather than getting in your way as it does in most object-oriented languages.

On the other hand, programming in Haskell is much less interactive than Lisp, and the tremendous amount of reflection present in languages like Lisp just doesn't fit the static view of the world that Haskell presupposes. The tool sets available to you are therefore quite different between the two languages, but hard to compare to one another.

I personally prefer the Lisp way of programming in general, as I feel it fits the way I work better. However, this doesn't mean you're bound to do so as well.

Short answer:

  • almost anything you can do with macros you can do with a higher-order function (and I include monads, arrows, etc.), but it might require more thinking (but only the first time, and it's fun and you'll be a better programmer for it), and
  • the static system is sufficiently general that it never gets in your way, and somewhat surprisingly it actually "aids in achieving agility" (as you said) because when your program compiles you can be almost certain that is correct, so this certainty lets you try out things you might be otherwise afraid to try -- there is a "dynamic" feel to programming although it's not the same as with Lisp.

[Note: There is a "Template Haskell" that lets you write macros just as in Lisp, but strictly speaking you should never need it.]

As I continue my Haskell-learning journey, it seems that one thing that helps "replace" macros is the ability to define your own infix operators and customize their precedence and associativity. Kinda complicated, but an interesting system!

There's less need for metaprogramming in Haskell than in Common Lisp because much can be structured around monads and the added syntax makes embedded DSLs look less tree-like, but there's always Template Haskell, as mentioned by ShreevatsaR, and even Liskell (Haskell semantics + Lisp syntax) if you like the parentheses.

In Haskell you can define an if function, which is impossible in LISP. This is possible because of laziness, which allows for more modularity in programs. This classic paper: Why FP matters by John Hughes, explains how laziness enhances composability.

I'm a Common Lisp programmer.

Having tried Haskell some time ago my personal bottom line was to stick with CL.

Reasons:

  • dynamic typing (check out Dynamic vs. Static Typing — A Pattern-Based Analysis by Pascal Costanza)
  • optional and keyword arguments
  • uniform homoiconic list syntax with macros
  • prefix syntax (no need to remember precedence rules)
  • impure and thus more suited for quick prototyping
  • powerful object system with meta-object protocol
  • mature standard
  • wide range of compilers

Haskell does have its own merits of course and does some things in a fundamentally different way, but it just doesn't cut it in the long term for me.

There are really cool things that you can achieve in Lisp with macros that are cumbersome (if possible) in Haskell. Take for example the `memoize' macro (see Chapter 9 of Peter Norvig's PAIP). With it, you can define a function, say foo, and then simply evaluate (memoize 'foo), which replaces foo's global definition with a memoized version. Can you achieve the same effect in Haskell with higher-order functions?

Concerning macros, here is a page which talk about it : Hello Haskell, Goodbye Lisp. It explains a point of view where macros are just not needed in Haskell. It comes with a short example for comparison.

Example case where a LISP macro is required to avoid evaluation of both arguments :

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

Example case where Haskell does not systematically evaluates both argument, without the need of anything like a macro definition :

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

And voilà