免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4284 | 回复: 5
打印 上一主题 下一主题

Haskell 的 List Comprehension 是如何工作的 [复制链接]

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-10-11 15:06 |只看该作者 |倒序浏览
Haskell 的 List Comprehension 是如何工作的

    本文通过在 Haskell 中生成 Fibonacci 数列这个例子讨论了 Haskell 中 list comprehension 是如何转换为 Haskell Kernel 的,借此可以理解 Haskell 的 list comprehension 是如何工作的。所谓的 Haskell Kernel 或者 Kernel 是 Haskell 98 Report 中描述的一种简单的 Functional 语言,和 GHC 中的 Core 类似。

一、例子

    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 来实现:

  1.         -- fib1.hs
  2.         -- fibonacci sequence, DOESN'T WORK
  3.         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 的位置试试:

  1.         -- fib2.hs
  2.         -- fibonacci sequence, DOESN'T WORK
  3.         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 后就是:

  1.         [ e | p <- l, Q ]       = let ok p = [ e | Q ]
  2.                                              ok _ = []
  3.                                          in concatMap ok l
复制代码

    其中 Q 是剩余的 qualifier。

    1) 对 fib1.hs 的解释

    按照上面的说法,

  1.         -- fib1.hs
  2.         fibs = 0 : 1 : [ x + y | x <- fibs, y <- tail fibs ]
复制代码

    会被转换为:

  1.         fibs = 0 : 1 :
  2.             let ok x = [ x + y | y <- tail fibs ]
  3.                  ok _ = []
  4.             in concatMap ok fibs
复制代码

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

  1.         ok x = let ok1 y = [ x + y ]
  2.                         ok1 _ = []
  3.                    in concatMap ok1 (tail fibs)
复制代码

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

  1.              head $ ok 0               -- x = 0
  2.         =   head $ ok1 1            -- y = 1
  3.         =   head $ [ 0 + 1 ]        -- ok1 的定义
复制代码

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

  1.              second $ ok 0             -- x = 0;x 的值不变
  2.         =   second $ ok1 1          -- y = 1; y 的值仍为 1
  3.         =   second $ [_, 0 + 1]
复制代码

    因此第四个数字仍为 1。

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

  1.              ok 0
  2.         =   concatMap ok1 (tail fibs)
  3.         =   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 扩展,则可采用如下实现:

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

  3.         module Fibonacci where

  4.         -- fibonacci sequence
  5.         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)


[ 本帖最后由 MMMIX 于 2008-10-11 23:22 编辑 ]

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
2 [报告]
发表于 2008-10-11 15:17 |只看该作者
代码实在太难对齐了 :em11:

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2008-10-11 20:31 |只看该作者
原帖由 MMMIX 于 2008-10-11 15:17 发表
代码实在太难对齐了 :em11:

呵呵,我给你改成用新宋体,看上去比以前好一些了。

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
4 [报告]
发表于 2008-10-11 23:27 |只看该作者
原帖由 flw 于 2008-10-11 20:31 发表

呵呵,我给你改成用新宋体,看上去比以前好一些了。

不过在编辑窗口太难预测最终的显示结果了,这给格式调整带来了许多问题。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
5 [报告]
发表于 2008-10-12 08:55 |只看该作者
原帖由 MMMIX 于 2008-10-11 23:27 发表

不过在编辑窗口太难预测最终的显示结果了,这给格式调整带来了许多问题。

我一般都是在 vim 里面调整好,然后复制上来。
只要 BBS 用的是等宽字体,并且发帖时使用了 code 标签,那么这样做一般可以保证对齐不变。

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
6 [报告]
发表于 2008-10-12 10:47 |只看该作者
原帖由 flw 于 2008-10-12 08:55 发表

我一般都是在 vim 里面调整好,然后复制上来。
只要 BBS 用的是等宽字体,并且发帖时使用了 code 标签,那么这样做一般可以保证对齐不变。

我试了下,这样也达不到预期效果(拷贝过来时就歪了 :em11: ),不知道怎么回事。

[ 本帖最后由 MMMIX 于 2008-10-12 10:49 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP