代码之家  ›  专栏  ›  技术社区  ›  Nathan

哈斯凯尔有什么好小题大做的?[关闭]

  •  106
  • Nathan  · 技术社区  · 6 年前

    我认识一些程序员,当他们在一起的时候,他们一直在谈论Haskell,在这里,每个人似乎都喜欢这种语言。擅长haskell似乎有点像天才程序员的标志。

    有人能举几个哈斯克尔的例子来说明它为什么如此优雅/卓越吗?

    17 回复  |  直到 7 年前
        1
  •  129
  •   Edward Z. Yang    16 年前

    对我来说,它的表达方式,以及我在haskell学习了一个月后认为是正确的,事实是功能编程以有趣的方式扭曲了你的大脑:它迫使你以不同的方式思考熟悉的问题:而不是循环,在地图、折叠和过滤器等方面思考,一般来说,如果你有更多比起从一个角度来看待一个问题,它使您能够更好地解释这个问题,并在必要时切换观点。

    关于Haskell,另一个真正有趣的地方是它的类型系统。它是严格类型化的,但是类型推理引擎让它感觉像一个Python程序,当你犯了一个愚蠢的与类型相关的错误时,它会神奇地告诉你。在这方面,haskell的错误信息有些欠缺,但是随着你越来越熟悉语言,你会对自己说:这就是打字应该是的!

        2
  •  132
  •   briangraf    9 年前

    这是 这个 说服我学习哈斯克尔的例子(孩子们,我很高兴我学习了)。

    -- 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一开始不会费心从文件中读取任何内容。

    事实上,哈斯克尔意识到 写文件 取决于 readFile ,因此能够优化此数据路径。

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

    …这看起来很像文件副本的有效C实现所能做的。

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

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

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

    2. 紧凑代码。减少了bug的行数。

    3. 编译错误。很多虫子只是 无效的haskell .

    哈斯克尔不适合所有人。但每个人都应该尝试一下。

        3
  •  64
  •   Brian    16 年前

    你问错了问题。

    哈斯克尔不是一种语言,你看几个很酷的例子,然后说“啊哈,我明白了, 那是 是什么让它变得好!”

    更像是,我们有所有其他的编程语言,它们或多或少的相似,还有哈斯克尔,完全不同和古怪的方式,一旦你习惯了这些古怪的东西,那就太棒了。但问题是,要适应这些怪癖需要相当长的时间。把哈斯克尔与几乎任何其他甚至半主流语言区分开来的东西:

    • 惰性评价
    • 没有副作用(一切都是纯的,IO/ETC通过monads发生)
    • 令人难以置信的表现静态类型系统

    以及一些不同于许多主流语言的其他方面(但也有一些共享):

    • 功能性
    • 显著空白
    • 推断类型

    正如其他一些海报所回答的那样,所有这些特性的组合意味着您将以一种完全不同的方式来考虑编程。因此,很难想出一个(或一组)能将这一点充分传达给Joe主流程序员的例子。这是一个经验的东西。(打个比方,我可以给你看我1970年中国之行的照片,但是看了照片之后,你仍然不知道在那期间住在那里是什么感觉。同样,我可以给你看哈斯克尔的“快速排序”,但你仍然不知道成为哈斯克尔意味着什么。)

        4
  •  24
  •   gtd    16 年前

    真正让haskell与众不同的是,它在设计中致力于加强函数式编程。你可以用几乎任何一种语言编写一个函数式的程序,但是在第一次方便的时候就太容易放弃了。haskell不允许您放弃函数式编程,因此您必须将其理解为逻辑结论,这是一个更容易理解的最终程序,并且回避了一类最棘手的bug。

    当涉及到为现实世界编写程序时,你可能会发现haskell缺乏一些实用的方式,但是你的最终解决方案将更好地了解haskell。我肯定还没到那里,但到目前为止,学习哈斯克尔比说Lisp在大学里更有启发性。

        5
  •  21
  •   Don Stewart    7 年前

    麻烦的一部分是,纯度和静态类型允许并行性与积极的优化相结合。并行语言现在很流行,多核有点破坏性。

    Haskell为您提供了比任何通用语言都多的并行性选项,以及一个快速的本地代码编译器。这种对平行样式的支持真的没有竞争:

    所以如果你想让你的多核工作,哈斯克尔有话要说。 一个很好的起点是西蒙·佩顿·琼斯。 tutorial on parallel and concurrent programming in Haskell .

        6
  •  18
  •   Curt Sampson    7 年前

    我花了去年的时间学习哈斯克尔,并写了一个相当大和复杂的项目。(该项目是一个自动化的期权交易系统,从交易算法到低层次、高速市场数据馈送的解析和处理都是在Haskell中完成的)。它比Java版本更简洁、更容易理解(对于那些有适当背景的人),而且非常健壮。

    对我来说,最大的胜利可能是能够通过诸如monoids、monads等东西模块化控制流。一个非常简单的例子是有序单倍体;在一个表达式中,例如

    c1 `mappend` c2 `mappend` c3
    

    哪里 c1 等着回来 LT , EQ GT , C1 返回 情商 使表达式继续,计算 c2 如果 C2 收益率 书信电报 燃气轮机 这就是整体的价值,而且 c3 未计算。这类事情在诸如单元消息生成器和解析器之类的事情中变得更加复杂和复杂,在这些事情中,我可能携带着不同类型的状态,具有不同的中止条件,或者可能希望能够为任何特定的调用决定中止是否真的意味着“没有进一步的处理”,或者意味着“在T时返回错误”。他结束了,但继续处理以收集进一步的错误消息。”

    这些都是需要一段时间和相当多的努力去学习的东西,因此,对于那些还不了解这些技术的人来说,很难做出令人信服的论据。我认为 All About Monads 教程给出了一个相当令人印象深刻的方面的演示,但我不希望任何人不熟悉的材料已经“得到它”的第一,甚至第三,仔细阅读。

    不管怎样,哈斯克尔还有很多其他的好东西,但这是一个我不经常提到的重要的东西,可能是因为它相当复杂。

        7
  •  17
  •   bmdhacks    16 年前

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

        8
  •  11
  •   GodEater    16 年前

    对于一个有趣的例子,您可以看到: http://en.literateprograms.org/Quicksort_(Haskell)

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

    与其他功能语言一样,哈斯克尔之所以如此有趣,是因为你必须以不同的方式思考如何编程。例如,通常不会使用for或while循环,而是使用递归。

    如前所述,haskell和其他功能语言擅长并行处理和编写应用程序,以便在多核上工作。

        9
  •  7
  •   maxaposteriori    16 年前

    我不能给你举个例子,我是一个OCAML的人,但是当我遇到像你这样的情况时,好奇心就起作用了,我必须下载一个编译器/解释器并尝试一下。你很可能通过这种方式了解到一种特定功能语言的优缺点。

        10
  •  7
  •   Dave    16 年前

    在处理算法或数学问题时,我发现一件很酷的事情是Haskell固有的对计算的懒惰评估,这是由于其严格的函数性质才可能实现的。

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

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

    结果实际上是一个无限列表。但是haskell会从左到右对它进行评估,只要你不尝试做一些需要整个列表的事情,你仍然可以使用它而不让程序陷入无限,例如:

    foo = sum $ takeWhile (<100) primes
    

    所有素数之和小于100。这有好几个原因。首先,我只需要编写一个生成所有素数的素数函数,然后就可以使用素数了。在面向对象的编程语言中,我需要某种方法来告诉函数在返回前应该计算多少素数,或者用对象模拟无限列表行为。另一件事是,通常情况下,您最终编写的代码表示您想要计算的内容,而不是按照哪个顺序来计算内容——相反,编译器会为您这样做。

    这不仅对无限列表有用,事实上,当不需要进行必要的评估时,它会在您不知道的情况下被使用。

        11
  •  6
  •   pkit    14 年前

    我同意其他人的观点,看到一些小例子并不是展示哈斯克尔的最佳方式。但我还是会给你一些。这是一个闪电般快速的解决方案 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)
    

    下面是一个完整的、可重用的 BubbleSearch 算法由lesh和mitzenmacher编写。我用它把大的媒体文件打包在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 并且理解算法,你会惊奇地发现,可以将算法打包成代码,而不必说你在搜索什么。

    在给了你一些你要求的例子之后,我会说 开始欣赏哈斯克尔的最佳方式 读给我写DVD打包机所需想法的那篇论文: Why Functional Programming Matters 约翰·休斯。这篇论文实际上早于哈斯克尔,但它很好地解释了一些让人喜欢哈斯克尔的想法。

        12
  •  5
  •   rpg    15 年前

    对我来说,Haskell的吸引力在于编译器的承诺。 放心 正确性。即使只是代码的一部分。

    我写了很多科学模拟代码,我想知道 所以 很多时候,如果我以前的代码中有一个bug,这会使很多当前的工作失效。

        13
  •  5
  •   Jonathan Fischoff    15 年前

    我发现,对于某些任务,我与哈斯克尔的合作非常有成效。

    这是因为它的语法简洁,而且易于测试。

    函数声明语法如下:

    FoA= A+ 5

    这是定义函数的最简单方法。

    如果我写反

    反向FOO A=A-5

    我可以通过写来检查它是否是任何随机输入的倒数。

    道具:双->bool
    prop_isinverse A=A==(反向FOO$FOO A)

    从命令行调用

    jonny@ubuntu:runhaskell quickcheck+姓名foofilename.hs

    它将通过随机测试100次so的输入来检查文件中的所有属性是否都被保存。

    我不认为haskell是适用于所有东西的完美语言,但是当涉及到编写小函数和测试时,我没有看到更好的语言。如果您的编程有一个数学组件,这是非常重要的。

        14
  •  3
  •   Dr. Watson    16 年前

    如果你能在哈斯克尔的打字系统里绕一圈,我认为这本身就是一个相当大的成就。

        15
  •  2
  •   Badri    16 年前

    它没有循环结构。很少有语言有这种特点。

        16
  •  1
  •   Jacob    16 年前

    我同意那些说函数式编程从另一个角度扭曲你的大脑来看待编程的说法。我只是把它当作业余爱好者,但我认为它从根本上改变了我处理问题的方式。我认为,如果没有接触到haskell(并在python中使用生成器和列表理解),我对linq的效果就不会那么好。

        17
  •  -1
  •   RossFabricant    16 年前

    发表一个相反的观点:史蒂夫耶格写道 Hindely-Milner languages lack the flexibility required to write good systems :

    H-M非常漂亮 无用的形式数学意义。它 处理一些计算构造 很好,模式匹配 在Haskell、SML和 OCAML特别方便。 不出所料,它能处理其他一些问题 常见且非常理想的结构 充其量是尴尬的,但他们解释了 通过这样说,这些场景就消失了 你错了,实际上你没有 想要它们。你知道,像,哦, 设置变量。

    哈斯克尔值得学习,但它也有自己的弱点。

    推荐文章