Chinaunix首页 | 论坛 | 博客
  • 博客访问: 311359
  • 博文数量: 48
  • 博客积分: 4510
  • 博客等级: 中校
  • 技术积分: 556
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-05 18:19
文章分类

全部博文(48)

文章存档

2012年(1)

2011年(9)

2010年(1)

2009年(12)

2008年(25)

分类:

2008-10-11 16:14:08

    本文通过在 Haskell 中生成 Fibonacci 数列这个例子讨论了 Haskell 中 list comprehension 是如何转换为
    Haskell Kernel 的,借此可以理解 Haskell 的 list comprehension 是如何工作的。

一、例子

    Fibonacci 数列的定义:

        F_n =   0                   if n =  0
                1                   if n =  1
                F_(n-1) + F_(n-2)   if n >= 2

    在 Haskell 中应该如何生成 F_n 呢?当然,可以直接将 F_n 的定义翻译为 Haskell 代码。但是,也可以使用
    list comprehension 来实现:

        -- fib1.hs
        -- fibonacci sequence, DOESN'T WORK
        fibs = 0 : 1 : [ x + y | x <- fibs, y <- tail fibs ]

    看起来这个实现是应该可以得到 Fibonacci 数列,可是取前十个数字时得到如下结果:

        > take 10 fibs
        [0,1,1,1,1,1,1,1,1,1]

    这个结果显然不对。交换下 x, y 的位置试试:

        -- fib2.hs
        -- fibonacci sequence, DOESN'T WORK
        fibs = 0 : 1 : [ x + y | y <- tail fibs, x <- fibs ]

    同样取前是个数字:

        > take 10 fibs
        [0,1,1,2,2,3,3,4,4,5]

    也不是期望的结果。为什么是这样呢? Haskell 中的 list comprehension 是如何工作的?

二、Haskell 中的 list comprehension 是如何工作的?

    按照 Haskell 98 Report Sect. 3.11 List Comprehension, Haskell 中 list comprehension 的一般形式为
    [ e | q_1, ... , q_n ] (n >= 1), 其中 q_i 称为 qualifier, 而 pat <- exp 这种形式的 qualifier 称为
    generator。

    [ e | q_1, ... , q_n ] 这个表达式的意义 Haskell 98 Report 是这样描述的:
        "Such a list comprehension returns the list of elements produced by evaluating e in the successive
        environments created by the nested, depth-first evaluation of the generators in the qualifier list."

    转换成 Haskell Kernel 后就是:

        [ e | p <- l, Q ]       = let ok p = [ e | Q ]
                                      ok _ = []
                                  in concatMap ok l
    其中 Q 是剩余的 qualifier。

    1) 对 fib1.hs 的解释

    按照上面的说法,

        -- fib1.hs
        fibs = 0 : 1 : [ x + y | x <- fibs, y <- tail fibs ]

    会被转换为:

        fibs = 0 : 1 :
            let ok x = [ x + y | y <- tail fibs ]
                ok _ = []
            in concatMap ok fibs

    而 ok x = [ x + y | y <- tail fibs ] 会进一步被转换为:

        ok x = let ok1 y = [ x + y ]
                   ok1 _ = []
               in concatMap ok1 (tail fibs)

    fibs 的第一、第二个数字分别是 0 和 1,那第三个呢?第三个数字是通过如下方式获取的:

            head $ ok 0            -- x = 0
        =   head $ ok1 1           -- y = 1
        =   head $ [ 0 + 1 ]       -- ok1 的定义

    因此第三个数字为 1。第四个数字通过如下方式获取:

            second $ ok 0           -- x = 0;x 的值不变
        =   second $ ok1 1          -- y = 1; y 的值仍为 1
        =   second $ [_, 0 + 1]

    因此第四个数字仍为 1。

    实际上,由于 fibs 本身是无穷数列,因此 fibs 的第三到第N个数字就是如下数列的第一到第(N-2)个数字:

            ok 0
        =   concatMap ok1 (tail fibs)
        =   concatMap (\y -> [ 0 + y ]) (tail fibs)

    另外,通过 ghc -ddump-ds -c fib1.hs 可以获取 fibs = 0 : 1 : [ x + y | x <- fibs, y <- tail fibs ]
    desugar 后的结果:

            ==================== Desugar ====================
            Rec {
            $dNum_aaT :: GHC.Num.Num GHC.Num.Integer
            []
            $dNum_aaT = GHC.Num.$f3
            Fib.fibs :: [GHC.Num.Integer]
            [Exported]
            []
            Fib.fibs =
              GHC.Base.:
                @ GHC.Num.Integer
                lit_a7r
                (GHC.Base.:
                   @ GHC.Num.Integer
                   lit_a7t
                   (__letrec {
                      ds_dgu :: [GHC.Num.Integer] -> [GHC.Num.Integer]
                      []
                      ds_dgu =
                        \ (ds_dgv :: [GHC.Num.Integer]) ->
                          case ds_dgv of ds_dgv {
                            [] -> GHC.Base.[] @ GHC.Num.Integer;
                            : ds_dgw ds_dgx ->
                              let {
                                x_a5G :: GHC.Num.Integer
                                []
                                x_a5G = ds_dgw } in
                              __letrec {
                                ds_dgy :: [GHC.Num.Integer] -> [GHC.Num.Integer]
                                []
                                ds_dgy =
                                  \ (ds_dgz :: [GHC.Num.Integer]) ->
                                    case ds_dgz of ds_dgz {
                                      [] -> ds_dgu ds_dgx;
                                      : ds_dgA ds_dgB ->
                                        let {
                                          y_a5I :: GHC.Num.Integer
                                          []
                                          y_a5I = ds_dgA
                                        } in  GHC.Base.: @ GHC.Num.Integer (+_aaC x_a5G y_a5I) (ds_dgy ds_dgB)
                                    };
                              } in  ds_dgy (GHC.List.tail @ GHC.Num.Integer fibs_a7n)
                          };
                    } in  ds_dgu fibs_a7n))
            $dNum_aaV :: GHC.Num.Num GHC.Num.Integer
            []
            $dNum_aaV = $dNum_aaT
            +_aaC :: GHC.Num.Integer -> GHC.Num.Integer -> GHC.Num.Integer
            []
            +_aaC = GHC.Num.+ @ GHC.Num.Integer $dNum_aaV
            fromInteger_aaU :: GHC.Num.Integer -> GHC.Num.Integer
            []
            fromInteger_aaU = fromInteger_aaS
            lit_a7t :: GHC.Num.Integer
            []
            lit_a7t = fromInteger_aaU (GHC.Num.S# 1)
            fromInteger_aaS :: GHC.Num.Integer -> GHC.Num.Integer
            []
            fromInteger_aaS = GHC.Num.fromInteger @ GHC.Num.Integer $dNum_aaT
            lit_a7r :: GHC.Num.Integer
            []
            lit_a7r = fromInteger_aaS (GHC.Num.S# 0)
            fibs_a7n :: [GHC.Num.Integer]
            []
            fibs_a7n = Fib.fibs
            end Rec }

    从中可以清楚的看到 list comprehension 被转换成了嵌套的 concatMap,以及上面 ok, ok1 的定义。

    2) fib2.hs 的解释

    fib2.hs 的情况和 fib1.hs 类似,留做练习 :-)

    fib1.hs 和 fib2.hs 无法得到正确的结果,皆是因为在 Haskell 的 list comprehension 中,对各个 generator
    的求值是有特定顺序的。那么,在 Haskell 中如何通过 list comprehension 生成 Fibonacci 数列?

三、在 Haskell 中如何通过 list comprehension 生成 Fibonacci 数列?

    如何只使用 Haskell 98,那么没有什么办法。但若使用 GHC 的 parallel list comprehension 扩展,则可采用如下实现:

        -- fib3.hs
        {-# LANGUAGE ParallelListComp #-}

        module Fibonacci where

        -- fibonacci sequence
        fibs = 0 : 1 : [ x + y | x <- fibs | y <- tail fibs ]

    取前十个数字:

        > take 10 fibs
        [0,1,1,2,3,5,8,13,21,34]

    是预期的结果。

    Parallel list comprehension 按照 GHC 手册(Sect. 8.3.4. Parallel List Comprehensions)的说法,是:

        "Parallel list comprehensions are a natural extension to list comprehensions. List comprehensions can
        be thought of as a nice syntax for writing maps and filters. Parallel comprehensions extend this to 
        include the zipWith family."

    这句话的具体意思参见 GHC 用户手册对 parallel list comprehension 的描述。另外也可参见 ghc 对 fib3.hs desugar
    后的结果。 

    最后,既然 parallel list comprehension 可看作是 zipWith 的 syntax sugar,那么 Fibonacci 数列自然也是可以通过
    直接使用 zipWith 来实现了,如下:

        -- fib4.hs
        -- fibonacci sequence
        fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
阅读(1361) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2008-11-25 03:56:39

谢谢博主讲解,haskell ,很不习惯》。 学起来有点慢...囧

chinaunix网友2008-10-11 16:16:00

Blog 的排版能力实在是太糟了