我知道有几个程序员在他们自己之间一直在讨论Haskell,所以每个人似乎都喜欢这种语言.擅长Haskell似乎有点像天才程序员的标志.
有人可以提供一些Haskell示例,说明为什么它如此优雅/优越?
这就是说服我学习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程序有什么不同呢?
答案是懒惰的评价.在大多数语言(甚至一些功能语言)中,如上所述的程序将导致整个文件被加载到内存中,然后以新名称再次写出.
哈斯克尔是"懒惰的".它不会在需要之前计算出事物,并且通过扩展不计算它永远不需要的东西.例如,如果您要删除该writeFile
行,Haskell首先不会从文件中读取任何内容.
实际上,Haskell意识到writeFile
依赖于readFile
,因此能够优化此数据路径.
虽然结果依赖于编译器,但运行上述程序时通常会发生这种情况:程序读取第一个文件的块(比如8KB),然后将其写入第二个文件,然后从第一个文件读取另一个块文件,并将其写入第二个文件,依此类推.(试试strace
吧!)
...看起来很像文件副本的高效C实现会做什么.
因此,Haskell允许您编写紧凑,可读的程序 - 通常不会牺牲很多性能.
我必须补充的另一件事是Haskell只是让编写错误的程序变得困难.令人惊讶的类型系统,缺乏副作用,当然Haskell代码的紧凑性减少了错误至少有三个原因:
更好的程序设计.复杂性降低导致逻辑错误减少.
紧凑的代码.减少存在错误的行数.
编译错误.很多bug 都不是有效的Haskell.
Haskell并不适合所有人.但是每个人都应该尝试一下.
这种方式对我而言,以及我在Haskell上学习一个月后的想法是正确的,因为函数式编程以有趣的方式扭曲了你的大脑:它迫使你以不同的方式思考熟悉的问题:而不是循环,在地图和折叠和过滤器等中思考.通常,如果您对问题有多个透视图,则可以更好地解决此问题,并根据需要切换视点.
关于Haskell的另一个非常巧妙的事情是它的类型系统.它是严格类型的,但类型推理引擎让它感觉像一个Python程序,当你做了一个与类型相关的愚蠢错误时,它会神奇地告诉你.Haskell在这方面的错误信息有点缺乏,但随着你越来越熟悉你会对自己说的语言:这就是打字应该是什么!
你有点问错误的问题.
Haskell不是一种语言,你可以去看几个很酷的例子然后去"啊哈,我现在看,这就是它的优点!"
更像是,我们拥有所有这些其他编程语言,并且它们或多或少相似,然后就是Haskell完全不同和古怪,一旦你习惯了古怪,它就会非常棒.但问题是,适应古怪需要一段时间.使Haskell与几乎任何其他甚至半主流语言区别开来的事情:
懒惰的评价
没有副作用(一切都是纯粹的,IO /等通过monad发生)
令人难以置信的表达静态类型系统
以及与许多主流语言不同的一些其他方面(但由一些人共享):
实用
重要的空白
类型推断
正如其他一些海报所回答的那样,所有这些功能的结合意味着您以完全不同的方式思考编程.因此很难提出一个例子(或一组例子),这些例子可以充分地与Joe-main-programmer进行沟通.这是一个体验式的事情.(为了比喻一下,我可以向你展示1970年中国之行的照片,但看完这些照片后,你仍然不知道在那段时间里住在那里是什么样的.同样,我可以告诉你一个Haskell 'quicksort',但你仍然不知道成为Haskeller是什么意思.)
真正让Haskell与众不同的是它在设计中为实现函数式编程所付出的努力.您可以使用几乎任何语言编写功能样式,但在第一次使用时很容易放弃.Haskell不允许你放弃函数式编程,所以你必须把它归结为逻辑结论,这是一个更容易推理的最终程序,并回避了一大类最棘手的bug.
当编写一个用于实际使用的程序时,您可能会发现Haskell缺乏某种实用的方式,但是最终的解决方案对于开始使用Haskell会更好.我肯定不在那里,但到目前为止,学习Haskell比说起来更有启发性,Lisp在大学里.
大惊小怪的一部分是纯度和静态类型使得并行性与积极的优化相结合.并行语言现在很热门,多核有点破坏性.
Haskell提供了更多的并行选项,而不是几乎任何通用语言,以及快速的本机代码编译器.这种对并行样式的支持确实没有竞争:
通过线程火花的半隐式并行性
显式线程
数据并行数组
演员和消息传递
交易记忆
因此,如果你关心让你的多核工作,Haskell有话要说.一个很好的起点是Simon Peyton Jones 关于Haskell中并行和并发编程的教程.
去年我花了很多时间学习Haskell并在其中编写一个相当大而复杂的项目.(该项目是一个自动化的期权交易系统,从交易算法到解析和处理低级,高速市场数据源的所有内容都在Haskell中完成.)它更加简洁易懂(适用于那些适当的背景)比Java版本,以及非常强大.
对我来说,最大的胜利可能是通过诸如幺半群,monad等等来模块化控制流的能力.一个非常简单的例子是Ordering monoid; 在诸如的表达中
c1 `mappend` c2 `mappend` c3
其中c1
等返回LT
,EQ
或者GT
,c1
返回EQ
原因表达继续,评估c2
; if c2
返回LT
或者GT
是整体的值,并且c3
不进行评估.像monadic消息生成器和解析器这样的东西变得相当复杂和复杂,我可能会携带不同类型的状态,有不同的中止条件,或者可能希望能够决定是否中止真正意味着任何特定的调用"没有进一步处理"或意味着"最后返回错误,但继续处理以收集进一步的错误消息."
这是所有需要花费一些时间的东西,可能需要花费很多精力来学习,因此对于那些不熟悉这些技术的人来说,很难为它做出令人信服的论证.我认为All About Monads教程给出了一个相当令人印象深刻的演示,但是我不希望任何不熟悉这些材料的人已经在第一次,甚至是第三次仔细阅读中"得到它".
无论如何,Haskell中还有许多其他好东西,但这是一个我不常见的常见的主要内容,可能是因为它相当复杂.
软件事务内存是处理并发的一种非常酷的方法.它比消息传递更灵活,而不像互斥体那样容易出现死锁. GHC对STM 的实施被认为是最好的之一.
有关一个有趣的例子,你可以看看:http: //en.literateprograms.org/Quicksort_(Hackask)
有趣的是以各种语言查看实现.
让Haskell与其他函数语言一起变得如此有趣的原因在于,您必须对如何编程进行不同的思考.例如,您通常不会使用for或while循环,但会使用递归.
如上所述,Haskell和其他功能语言在并行处理和编写应用程序以在多核上工作方面表现优异.
我不能给你一个例子,我是一个OCaml家伙,但是当我遇到这样的情况时,好奇心刚刚掌握,我必须下载一个编译器/解释器并试一试.您可能会更多地了解给定功能语言的优点和缺点.
在处理算法或数学问题时,我发现非常酷的一件事是Haskell对计算的固有惰性评估,这只能由于其严格的功能性而成为可能.
例如,如果要计算所有素数,可以使用
primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]
结果实际上是一个无限的列表.但是Haskell会从左边开始评估它,所以只要你不尝试做一些需要整个列表的东西,你仍然可以使用它而不会让程序陷入无穷大,例如:
foo = sum $ takeWhile (<100) primes
它总和所有小于100的素数.这很好,有几个原因.首先,我只需编写一个生成所有素数的素数函数,然后我就可以使用素数了.在面向对象的编程语言中,我需要一些方法来告诉函数在返回之前应该计算多少素数,或者用对象模拟无限列表行为.另一件事是,一般来说,你最终编写的代码表达了你想要计算的东西,而不是用于评估事物的顺序 - 相反,编译器会为你做这些.
这不仅对无限列表有用,实际上它是在没有必要时不需要进行评估的情况下一直使用的.
我同意其他人的观点,看到一些小例子并不是展示Haskell的最佳方式.但无论如何我会给一些.这是Euler Project问题18和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的博客文章并理解该算法,您会惊讶于可以将算法打包到代码中,而无需说明您要搜索的内容.
根据你的要求给了你一些例子,我会说开始欣赏Haskell的最好方法是阅读那些给我写DVD包装器所需的想法的论文:为什么功能编程很重要 John Hughes.这篇论文实际上早于Haskell,但它巧妙地解释了一些让人们喜欢Haskell的想法.
对我来说,Haskell的吸引力在于编译器保证正确性的承诺.即使它是代码的纯部分.
我已经写了很多的科学模拟代码,并想知道这么多次,如果有在我以前的代码中的错误,这可能无效了很多当前的工作.
我发现对于某些任务,我使用Haskell非常高效.
原因在于简洁的语法和易于测试.
这就是函数声明语法的含义:
foo a = a + 5
这是我能想到定义函数的最简单方法.
如果我写逆
inverseFoo a = a - 5
我可以通过写入来检查它是否是任何随机输入的反转
prop_IsInverse :: Double - > Bool
prop_IsInverse a = a ==(inverseFoo $ foo a)
并从命令行调用
jonny @ ubuntu:runhaskell quickCheck + names fooFileName.hs
这将检查我的文件中的所有属性是否被保留,通过随机测试输入一百次.
我不认为Haskell是所有内容的完美语言,但是当谈到编写小函数和测试时,我还没有看到更好的东西.如果您的编程有数学成分,这非常重要.