Haskell有什么大惊小怪的?

时间:2009-04-22 04:45:47

标签: haskell functional-programming

我知道有些程序员在他们自己之间一直在谈论Haskell,而在这里所有人似乎都喜欢这种语言。擅长Haskell似乎有点像天才程序员的标志。

有人可以提供一些Haskell示例,说明为什么它如此优雅/出众?

17 个答案:

答案 0 :(得分:133)

这是 的例子,说服我学习Haskell(男孩,我很高兴我做过)。

-- program to copy a file --
import System.Environment

main = do
         --read command-line arguments
         [file1, file2] <- getArgs

         --copy file contents
         str <- readFile file1
         writeFile file2 str

好的,这是一个简短易读的程序。从这个意义上说,它比C程序更好。但是,与具有非常相似结构的Python程序有什么不同呢?

答案是懒惰的评价。在大多数语言(甚至一些功能语言)中,如上所述的程序将导致整个文件被加载到内存中,然后以新名称再次写出。

Haskell“懒惰”。它不会在需要之前计算事物,并且通过扩展计算它永远不需要的东西。例如,如果您要删除writeFile行,Haskell首先不会从文件中读取任何内容。

实际上,Haskell意识到writeFile取决于readFile,因此可以优化此数据路径。

虽然结果依赖于编译器,但运行上述程序时通常会发生这种情况:程序读取第一个文件的块(比如8KB),然后将其写入第二个文件,然后读取另一个块从第一个文件,并将其写入第二个文件,依此类推。 (尝试在其上运行strace!)

...看起来很像文件副本的高效C实现会做什么。

因此,Haskell允许您编写紧凑,可读的程序 - 通常不会牺牲很多性能。

我必须补充的另一件事是Haskell只是让编写错误程序变得困难。令人惊讶的类型系统,缺乏副作用,当然还有Haskell代码的紧凑性,至少有三个原因可以减少错误:

  1. 更好的程序设计。降低复杂性可以减少逻辑错误。

  2. 紧凑的代码。减少存在错误的行。

  3. 编译错误。 的许多错误都不是有效的Haskell

  4. Haskell并不适合所有人。但是每个人都应该试一试。

答案 1 :(得分:130)

它向我倾斜的方式,以及我在Haskell上学习一个月后的想法是正确的,这是因为函数式编程以有趣的方式扭曲了你的大脑:它迫使你去思考熟悉的问题以不同的方式:而不是循环,思考地图和折叠和过滤器等。通常,如果您对问题有多个透视图,则可以更好地解决此问题,并根据需要切换视点。 / p>

关于Haskell的另一个非常巧妙的事情是它的类型系统。它是严格类型的,但类型推理引擎让它感觉像一个Python程序,当你做了一个与类型相关的愚蠢错误时,它会神奇地告诉你。 Haskell在这方面的错误信息有点缺乏,但随着你越来越熟悉你会对自己说的语言:这就是打字应该是什么!

答案 2 :(得分:65)

你有点问错误的问题。

Haskell不是一种语言,你可以去看几个很酷的例子然后去“啊哈,我现在看, 是什么让它变得更好!”

更像是,我们拥有所有这些其他编程语言,并且它们或多或少相似,然后就是Haskell完全不同和古怪,一旦你习惯了古怪,就会非常棒。但问题是,适应古怪需要一段时间。使Haskell与几乎任何其他甚至半主流语言区别开来的事情:

  • 懒惰评估
  • 没有副作用(一切都是纯粹的,IO /等通过monad发生)
  • 令人难以置信的表达式静态类型系统

以及与许多主流语言不同的一些其他方面(但由一些人共享):

  • 功能
  • 重要的空白
  • 类型推断

正如其他一些海报所回答的那样,所有这些功能的结合意味着您以完全不同的方式思考编程。因此很难想出一个例子(或一组例子),这些例子足以将其与Joe-main-programmer进行充分沟通。这是一个体验式的事情。 (为了比喻一下,我可以向你展示1970年中国之行的照片,但看完这些照片后,你仍然不知道在那段时间住在那里是什么样的。同样,我可以告诉你一个Haskell 'quicksort',但你仍然不知道成为Haskeller意味着什么。)

答案 3 :(得分:25)

真正让Haskell与众不同的是它在设计中为实现函数式编程所付出的努力。您可以使用几乎任何语言编写功能样式,但在第一次使用时很容易放弃。 Haskell不允许你放弃函数式编程,因此你必须把它归结为逻辑结论,这是一个更容易推理的最终程序,并回避了一大类最棘手的bug。

在编写用于实际使用的程序时,您可能会发现Haskell缺乏某种实用的方式,但是最终的解决方案对于开始使用Haskell会更好。我肯定不在那里,但到目前为止,学习Haskell比说起来更有启发性,Lisp在大学里。

答案 4 :(得分:22)

部分烦恼是纯度和静态类型使得并行性与积极的优化相结合。并行语言现在很热门,因为多核有点破坏性。

Haskell提供了更多的并行选项,而不是几乎任何通用语言,以及快速的本机代码编译器。这种对并行样式的支持确实没有竞争:

因此,如果你关心让你的多核工作,Haskell有话要说。 一个很好的起点是Simon Peyton Jones'tutorial on parallel and concurrent programming in Haskell

答案 5 :(得分:18)

我在去年学习Haskell并在其中编写了一个相当大而复杂的项目。 (该项目是一个自动化的期权交易系统,从交易算法到解析和处理低级,高速市场数据源的所有内容都在Haskell中完成。)它更加简洁易懂(适用于那些适当的背景),而不是Java版本,以及非常强大。

对我来说,最大的胜利可能是通过诸如幺半群,monad等等来模块化控制流的能力。一个非常简单的例子是Ordering monoid;在诸如

之类的表达中
c1 `mappend` c2 `mappend` c3

其中c1等等返回LTEQGTc1返回EQ会导致表达式继续,评估{ {1}};如果c2返回c2LT这是整体的值,则GT不会被评估。像monadic消息生成器和解析器这样的东西变得相当复杂和复杂,我可能会携带不同类型的状态,有不同的中止条件,或者可能希望能够决定是否中止真正意味着任何特定的调用“没有进一步处理”或意味着“最后返回错误,但继续处理以收集更多错误消息。”

这是所有需要花费一些时间的东西,而且可能需要付出相当大的努力才能学习,因此对于那些不熟悉这些技术的人来说,很难为它做出令人信服的论证。我认为All About Monads教程给出了一个相当令人印象深刻的证明,但是我不希望任何不熟悉这些材料的人已经在第一个,甚至第三个,“小心翼翼”读数。

无论如何,Haskell还有很多其他的好东西,但这是一个我不经常提到的主要内容,可能是因为它相当复杂。

答案 6 :(得分:17)

Software Transactional Memory是一种很酷的处理并发的方法。它比消息传递更灵活,而不像互斥体那样容易出现死锁。 GHC's STM的实施被认为是最好的之一。

答案 7 :(得分:11)

有关一个有趣的例子,你可以看看: http://en.literateprograms.org/Quicksort_(Haskell)

有趣的是以各种语言查看实现。

使Haskell与其他函数语言一起变得如此有趣的原因在于,您必须对如何编程进行不同的思考。例如,您通常不会使用for或while循环,但会使用递归。

如上所述,Haskell和其他功能语言在并行处理和编写应用程序方面优于多核。

答案 8 :(得分:7)

我不能给你一个例子,我是一个OCaml家伙,但是当我遇到你自己的情况时,好奇心刚刚掌握,我必须下载一个编译器/解释器并试一试。您可能会更多地了解给定功能语言的优点和缺点。

答案 9 :(得分:7)

在处理算法或数学问题时,我发现非常酷的一件事是Haskell对计算的固有懒惰评估,这只能由于其严格的功能性而可能。

例如,如果要计算所有素数,可以使用

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]

,结果实际上是一个无限的列表。但是Haskell会从左边开始评估它,所以只要你不尝试做一些需要整个列表的东西,你仍然可以使用它而不会让程序陷入无穷大,例如:

foo = sum $ takeWhile (<100) primes

总和小于100的所有素数。这很好,有几个原因。首先,我只需编写一个生成所有素数的素数函数,然后我就可以使用素数了。在面向对象的编程语言中,我需要一些方法来告诉函数在返回之前应该计算多少素数,或者用对象模拟无限列表行为。另一件事是,一般来说,你最终编写的代码表达了你想要计算的东西,而不是用于评估事物的顺序 - 相反,编译器会为你做这些。

这不仅对无限列表有用,实际上它在没有你不需要进行超出必要评估的情况下一直不知道的情况下使用。

答案 10 :(得分:6)

我同意其他人的观点,看到一些小例子并不是展示Haskell的最佳方式。但无论如何我会给一些。这是Euler Project problems 18 and 67的一个快速解决方案,它要求您找到从三角形的基点到顶点的最大和路径:

bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
  where bu [bottom]     = bottom
        bu (row : base) = merge row $ bu base
        merge [] [_] = []
        merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)

这是Lesh和Mitzenmacher对BubbleSearch算法的完整,可重用的实现。我用它来装载大型媒体文件,以便在DVD上存档,而不会浪费:

data BubbleResult i o = BubbleResult { bestResult :: o
                                     , result :: o
                                     , leftoverRandoms :: [Double]
                                     }
bubbleSearch :: (Ord result) =>
                ([a] -> result) ->       -- greedy search algorithm
                Double ->                -- probability
                [a] ->                   -- list of items to be searched
                [Double] ->              -- list of random numbers
                [BubbleResult a result]  -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
    where bubble order rs = BubbleResult answer answer rs : walk tries
            where answer = search order
                  tries  = perturbations p order rs
                  walk ((order, rs) : rest) =
                      if result > answer then bubble order rs
                      else BubbleResult answer result rs : walk rest
                    where result = search order

perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
    where xr' = perturb xs rs
          perturb :: [a] -> [Double] -> ([a], [Double])
          perturb xs rs = shift_all p [] xs rs

shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
  where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
        shift_one new' xs rs k = shift new' [] xs rs
          where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
                shift new' prev' (x:xs) (r:rs) 
                    | r <= p    = k (x:new') (prev' `revApp` xs) rs
                    | otherwise = shift new' (x:prev') xs rs
                revApp xs ys = foldl (flip (:)) ys xs

我确定这段代码看起来像随机乱码。但是,如果您阅读Mitzenmacher's blog entry并了解算法,您会惊讶于可以将算法打包到代码中,而无需说明您要搜索的内容。

根据你的要求给你一些例子,我会说开始欣赏Haskell 的最佳方式是阅读给我提供编写DVD打包器所需的创意的论文:约翰休斯Why Functional Programming Matters。这篇论文实际上早于Haskell,但它巧妙地解释了一些让人们喜欢Haskell的想法。

答案 11 :(得分:5)

我发现对于某些任务,我使用Haskell非常高效。

原因在于简洁的语法和易于测试。

这就是函数声明语法的含义:

  

foo a = a + 5

这是我能想到定义函数的最简单方法。

如果我写反向

  

inverseFoo a = a - 5

我可以通过编写

来检查它是否是任何随机输入的反转
  

prop_IsInverse :: Double - &gt;布尔
  prop_IsInverse a = a ==(inverseFoo $ foo a)

从命令行调用

  

jonny @ ubuntu:runhaskell quickCheck + names fooFileName.hs

通过随机测试输入数百次来检查我文件中的所有属性是否都被保留。

我认为Haskell并不是所有内容的完美语言,但在编写小函数和测试时,我还没有看到更好的东西。如果您的编程具有数学成分,则这非常重要。

答案 12 :(得分:5)

对我来说,Haskell的吸引力在于编译器保证正确性的承诺。即使它是代码的纯部分。

我已经编写了很多科学模拟代码,并且如果我以前的代码中存在错误,那么多次想知道如此,这可能会使许多当前的工作失效。

答案 13 :(得分:3)

如果你可以围绕Haskell中的类型系统,我认为这本身就是一个很大的成就。

答案 14 :(得分:2)

它没有循环结构。没有多少语言有这个特点。

答案 15 :(得分:1)

我同意那些说功能性编程能够让你的大脑从不同角度看编程的人。我只把它用作业余爱好者,但我认为它从根本上改变了我解决问题的方式。如果没有暴露给Haskell(并在Python中使用生成器和列表推导),我认为LINQ几乎没有那么有效。

答案 16 :(得分:-1)

提出逆向观点:Steve Yegge写道Hindely-Milner languages lack the flexibility required to write good systems

  

H-M非常漂亮,完全是   无用的正式数学意义。它   处理一些计算结构   很友好地;模式匹配   在Haskell,SML和   OCaml特别方便。   不出所料,它处理其他一些问题   常见的和非常理想的结构   尴尬,但他们解释   通过这样说的那些场景   你错了,你实际上并没有   要他们。你知道吗,哦,   设置变量。

Haskell值得学习,但它有自己的弱点。