Answers:
它向我介绍的方式,以及我在Haskell上学习一个月后的真实想法,是函数式编程以有趣的方式扭曲了您的大脑:它迫使您以不同的方式思考熟悉的问题:而不是循环,请考虑地图,折叠和过滤器等。通常,如果您对一个问题有多个观点,则可以使您更好地推理此问题,并根据需要切换观点。
Haskell的另一个真正的优点是它的类型系统。它是严格类型化的,但是类型推断引擎使它看起来像一个Python程序,可以神奇地告诉您何时犯了与类型相关的愚蠢错误。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程序有何不同?
答案是懒惰的评估。在大多数语言(甚至是某些功能语言)中,如上一种结构的程序会导致将整个文件加载到内存中,然后以新名称再次写出。
Haskell是“懒惰的”。直到需要时,它才计算内容,并且通过扩展,它也不再计算它不需要的内容。例如,如果要删除该writeFile
行,Haskell首先不会费心从文件中读取任何内容。
实际上,Haskell意识到writeFile
取决于readFile
,因此能够优化此数据路径。
虽然结果取决于编译器,但运行上述程序时通常会发生以下情况:该程序读取第一个文件的一个块(例如8KB),然后将其写入第二个文件,然后从第一个文件读取另一个块文件,并将其写入第二个文件,依此类推。(尝试运行strace
!)
...看起来很像文件副本的高效C实现。
因此,Haskell使您可以编写紧凑,易读的程序-通常不会牺牲很多性能。
我必须补充的另一件事是,Haskell使得编写错误的程序变得很困难。令人惊叹的类型系统,没有副作用,当然Haskell代码的紧凑性至少在三个方面减少了错误:
更好的程序设计。降低的复杂度导致更少的逻辑错误。
紧凑的代码。出现错误的行减少了。
编译错误。许多错误只是无效的Haskell。
Haskell并非适合所有人。但是每个人都应该尝试一下。
hSetBuffering handle (BlockBuffering (Just bufferSize))
。
Data.Bytestring.Lazy.readFile
),而这与Haskell是一种惰性(非严格)语言无关。Monad正在按顺序排列-大致意味着“取出结果时,所有副作用都已完成”。至于“惰性字节串”魔术:这很危险,您可以使用大多数其他语言中的相似或更简单的语法来做到这一点。
readFile
也以相同的方式Data.ByteString.Lazy.readFile
执行惰性IO 。因此,答案是正确的,不仅是编译器优化。的确,这是Haskell规范的一部分:“该readFile
函数读取文件并以字符串形式返回文件的内容。该文件按需延迟读取,就像使用getContents
。一样。”
const fs = require('fs'); const [file1, file2] = process.argv.slice(2); fs.createReadStream(file1).pipe(fs.createWriteStream(file2))
。Bash也有类似的东西:cat $1 > $2
您在问一个错误的问题。
Haskell不是一门语言,您可以看一些很酷的例子然后说:“啊哈,我现在知道了,这就是使它变得更好的原因!”
这更像是,我们拥有所有其他所有编程语言,它们或多或少都相似,然后就是Haskell,它完全不同且古怪,一旦您习惯了古怪性就完全棒了。但是问题是,要花很长时间才能适应这种古怪性。使Haskell与几乎任何其他偶半主流语言区分开的事情:
以及与许多主流语言不同(但某些语言共有)的其他一些方面:
正如其他海报所回答的那样,所有这些功能的组合意味着您以完全不同的方式考虑编程。因此很难想出一个(或一组)示例来充分地将此信息传达给Joe-mainstream-programmer。这是一个体验性的事情。(举个比喻,我可以给你看我1970年中国之行的照片,但是看到这些照片后,您仍然不知道那段时间住在那儿的感觉如何。类似地,我可以给你看一个Haskell “快速排序”,但您仍然不知道成为Haskeller意味着什么。)
真正使Haskell与众不同的是它在设计中为执行功能性编程而付出的努力。您可以使用几乎任何语言编写功能性风格的程序,但是要在第一时间放弃它太容易了。Haskell不允许您放弃函数式编程,因此您必须将其总结为合乎逻辑的结论,这是一个易于推理的最终程序,可以避免一整类最棘手的错误。
在编写供现实世界使用的程序时,您可能会发现Haskell缺乏实用性,但最终的解决方案对于了解Haskell会更好。我绝对还没有,但是到目前为止,学习Haskell比说Lisp上大学要有意义得多。
unsafePerformIO
只想观看世界燃烧的人;)
大惊小怪的一部分是,纯净和静态类型化使并行性与积极的优化相结合。并行语言现在很热门,多核有点破坏性。
与几乎所有通用语言相比,Haskell为您提供了更多的并行性选项,并提供了快速的本机代码编译器。对并行样式的这种支持确实没有竞争:
因此,如果您关心使多核工作正常,那么Haskell有话要说。Simon Peyton Jones的Haskell并行和并发编程教程是一个很好的起点。
去年我花了很多时间学习Haskell,并在其中编写了一个相当大而复杂的项目。(该项目是一个自动的期权交易系统,从交易算法到解析和处理低水平,高速市场数据馈送的所有工作均在Haskell中完成。)它更加简洁明了(对于那些拥有适当的背景)和Java版本一样强大。
对我而言,最大的赢家可能是能够将诸如monoid,monad等之类的控制流模块化。一个非常简单的示例是Ordering monoid;在诸如
c1 `mappend` c2 `mappend` c3
其中c1
等返回LT
,EQ
或者GT
,c1
返回EQ
原因表达继续,评估c2
; if c2
返回LT
或GT
那是整体的价值,并且c3
不被评估。这种事情在诸如monadic消息生成器和解析器之类的事情上变得更加复杂和复杂,在这些情况下,我可能会携带不同类型的状态,具有变化的中止条件,或者可能希望能够为任何特定的调用确定中止是否真正意味着“没有进一步的处理”或“最终返回错误,但继续进行处理以收集更多的错误消息”。
这些都是需要花费一些时间和精力的东西,因此对于那些尚不了解这些技术的人来说,很难为其说服力。我认为《关于Monads的全部》教程很好地展示了这一方面,但是我不希望任何不熟悉该材料的人都会在仔细阅读第一甚至第三本书时就“理解”它。
无论如何,Haskell中还有很多其他好东西,但这是我经常看到的一个重要的东西,可能是因为它很复杂。
对于一个有趣的示例,您可以查看:http : //en.literateprograms.org/Quicksort_(Haskell)
有趣的是看各种语言的实现。
与其他功能语言一样,Haskell如此有趣的原因是您必须对编程方式进行不同的思考。例如,您通常不使用for或while循环,而是使用递归。
如上所述,Haskell和其他功能语言在并行处理和编写可在多核上工作的应用程序方面表现出色。
在处理算法或数学问题时,我发现很酷的一件事是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的工作效率非常高。
原因是因为语法简洁且易于测试。
这就是函数声明语法:
foo a = a + 5
这是定义函数的最简单方法。
如果我写反
inverseFoo a = a-5
我可以通过写来检查它是否是任何随机输入的逆
prop_IsInverse :: Double->布尔
prop_IsInverse a = a ==(inverseFoo $ foo a)
并从命令行调用
jonny @ ubuntu:runhaskell quickCheck +名称fooFileName.hs
通过随机测试输入的一百次,它将检查文件中的所有属性是否都保留。
我认为Haskell并不是适用于所有事物的完美语言,但是当涉及到编写小的函数和测试时,我没有发现更好的东西。如果您的程序具有数学成分,则这非常重要。
如果您可以全神贯注于Haskell的类型系统,那么我认为它本身就是一个成就。
提出相反的观点:Steve Yegge写道Hindely-Milner语言缺乏编写良好系统所需的灵活性:
HM非常漂亮,完全没有形式化的数学意义。它很好地处理了一些计算结构;在Haskell,SML和OCaml中找到的模式匹配调度特别方便。毫不奇怪,它充其量只能处理笨拙的其他一些常见且非常可取的构造,但是它们通过说错了,实际上并不需要它们来解释这些情况。您知道,设置变量等。
Haskell值得学习,但是有其自身的弱点。