免费注册 查看新帖 |

Chinaunix

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

[ML, Scheme] 无穷表, Stream [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-11-07 10:16 |只看该作者 |倒序浏览
本贴内容来自 《ML 程序设计语言》(Paulson) 的第 5 章:函数和无穷数据。

在惰性求值语言中如 Haskell 中,惰性是内建的,所以能轻易定义无穷数据。而在严格求值的语言中,要实现惰性求值,就要花费额外的工夫。SICP 第三章最后一节 Strem,讨论的就是这个问题。本贴给出几个在 ML 中实现无穷表的例子,可以与 SICP 对照着看一下:它们的手段是类似的。

[ 本帖最后由 win_hate 于 2008-11-7 16:03 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2008-11-07 10:29 |只看该作者

Scheme

在 Scheme 中,惰性求值是通过 delay 来实现的。如果想延迟表达式 E 的求值,可以构造找一个无参函数 (lambda () E),在需要时,调用这个函数就可以得到 E 的值.

困难在于,如何构造 (lambda () E) 呢?如果把 delay 实现为函数,比如 (delay (E) (lambda () E)),这样是不行的。因为此时 E 是 delay 的参数,按 scheme 的严格求值方式,在构造 (lambda () E) 之前,E 已经被求值了。

绕过这个困难的一个方法为使用特殊形式,即宏。下面是一个“山寨版” 的 delay 和 force,后者取消延迟


  1. (define-syntax delay
  2.   (syntax-rules ()
  3.     ((_ x) (lambda () x))))

  4. (define (force x)
  5.   (x))
复制代码


SICP 中还提到了一些优化手段,不过在这里不关心它们。现在我们知道,SICP 中给出的惰性手段就是 delay,它有两个要点:宏和无参函数。

[ 本帖最后由 win_hate 于 2008-11-7 10:37 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2008-11-07 10:57 |只看该作者

ML 例子 1

下面我们来看一个 ML 实现惰性表的例子


  1. datatype 'a seq = Nil | Cons of 'a * (unit-> 'a seq);
复制代码


  • datatype 定义数据类型和相应的建构函数
  • unit-> 'a seq 是一个无参函数类型


对 Cons (x, y), 利用 y() 就可以得一个新表。这个新表正好是无穷序列 Cons(x,y) 中 x 之后的部分。

表头插入函数 cons


  1. fun cons x xq = Cons (x, fn()=>xq);
复制代码


car 和 cdr


  1. exception Empty
  2. fun car (Cons(x, _)) = x
  3.   | car Nil = raise Empty

  4. fun cdr (Cons(_, y)) = y()
  5.   | cdr Nil = raise Empty
复制代码


既然定义了表尾 Nil,这套机制应该可以用在有穷表上:


  1. - val x = cons 1 (cons 2 (cons 3 Nil));
  2. > val x = Cons(1, fn) : int seq

  3. - car x;
  4. > val it = 1 : int

  5. - car (cdr x);
  6. > val it = 2 : int

  7. - car (cdr (cdr (cdr x)));
  8. ! Uncaught exception:
  9. ! Empty
  10. -
复制代码


构造一个无穷表? 比如 from k,生成序列:k, k+1, k+2, ...

看上去应该是:


  1. - fun from k = cons (k, from (k+1))
复制代码


但这是不行的,应为 from (k+1) 现在是 cons 的参数。cons 是 ML 中一个自定义函数,它的求值方式是严格的。在 Scheme 中有宏,在 ML 中有 datatype


  1. fun from k = Cons (k, fn()=>from (k+1));
复制代码


直接用 'a seq 的构造子 Cons 就可以了。

试一下


  1. - val N=from 0;
  2. > val N = Cons(0, fn) : int seq

  3. - car N;
  4. > val it = 0 : int

  5. - car (cdr N);
  6. > val it = 1 : int

  7. - car (cdr (cdr N));
  8. > val it = 2 : int
复制代码


再来个 take,


  1. fun take xq 0 = []
  2.   | take Nil _ = raise Subscript
  3.   | take xq n = (car xq) :: (take (cdr xq) (n-1));
复制代码


  1. - take N 10;
  2. > val it = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] : int list
复制代码


(先到这里,欢迎拍砖).

[ 本帖最后由 win_hate 于 2008-11-7 11:37 编辑 ]

论坛徽章:
0
4 [报告]
发表于 2008-11-09 10:49 |只看该作者

ML 例子 2

没人拍砖,自己拍一下。

上面构建的流,流首总是被预先求出的,所以这个方案不够懒惰。书本的习题提供了另一个方案


  1. datatype 'a seq = Nil | Cons of unit->'a * 'a seq;
复制代码


此时流首和后继都是通过一个无参函数返回的。cons, car, cdr 修改如下:


  1. fun cons x xq = Cons (fn()=> (x, xq));

  2. exception Empty
  3. fun car (Cons(f)) = #1 (f())
  4.   | car Nil = raise Empty

  5. fun cdr (Cons(f)) = #2 (f())
  6.   | cdr Nil = raise Empty
复制代码


对一个有穷表试一下:


  1. - val x = cons 1 (cons 2 Nil);
  2. > val x = Cons fn : int seq

  3. - car x;
  4. > val it = 1 : int

  5. - cdr x;   
  6. > val it = Cons fn : int seq

  7. - (car o cdr) x;
  8. > val it = 2 : int

  9. - (car o cdr o cdr) x;
  10. ! Uncaught exception:
  11. ! Empty
复制代码


构造一个无穷表生成函数 from,配套一个 take


  1. fun from k = Cons (fn()=> (k, from (k+1)));

  2. fun take xq 0 = []
  3.   | take Nil n = raise Subscript
  4.   | take xq n = (car xq)::(take (cdr xq) (n-1));
复制代码


效果:


  1. - val N=from 0;
  2. > val N = Cons fn : int seq

  3. - take N 10;
  4. > val it = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] : int list
复制代码

论坛徽章:
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
5 [报告]
发表于 2008-11-09 13:39 |只看该作者
win_hate  有研究过 PLT DrScheme 中的 lazy scheme 么?

论坛徽章:
0
6 [报告]
发表于 2008-11-09 15:53 |只看该作者
原帖由 MMMIX 于 2008-11-9 13:39 发表
win_hate  有研究过 PLT DrScheme 中的 lazy scheme 么?


没有。我有两个猜测

  • 从 mzscheme 运行时打印出的信息来看,它在读到 #lang lazy 后加载了一堆库。因此猜测它也就用了类似 SICP 中的 delay 和 force. 而且我记得看到一些数据是 promise.
  • 但在 mzscheme 中对 cons 求值可以看到它是个 primitive-procedure 而不是宏。因此猜测它本身是 lazy 的,也许读到  #lang lazy 后加载了一个独立的解析器。


这两个猜测是矛盾的,不知道哪个对。

论坛徽章:
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
7 [报告]
发表于 2008-11-09 17:51 |只看该作者

回复 #6 win_hate 的帖子

我一直挺好奇 PLT Scheme 的 lazy scheme 是怎么实现的。

论坛徽章:
0
8 [报告]
发表于 2008-11-09 18:23 |只看该作者
SICP 第四章讨论如何用 Scheme 的内建机制实现一个 Scheme 解释器。其中 4.2 讨论惰性求值,也就是用严格 Scheme 解释器实现一个惰性 Scheme 解释器。

论坛徽章:
0
9 [报告]
发表于 2008-11-10 23:58 |只看该作者

继续,继续,最后一个例子

书本的习题还提供了一种 “更具惰性” 的实现


  1. datatype 'a seqnode = Nil
  2.                     | Cons of 'a * 'a seq

  3. and 'a seq = Seq of unit-> 'a seqnode;
复制代码


这个实现有两种而不是一种类型:seq 和 seqnode。每次对 seq 求值得到一个 node, 而一个 node 是一个 'a 类型和 seq 的对。

cons

  1. fun cons x xq = Seq(fn()=> Cons(x, xq));
复制代码


试下先:


  1. - cons 1 Nil;
  2. ! Toplevel input:
  3. ! cons 1 Nil;
  4. !        ^^^
  5. ! Type clash: expression of type
  6. !   'a seqnode
  7. ! cannot have type
  8. !   int seq
  9. -
复制代码


bump! 出错了。

cons 1 Nil 太顺手了,lisp 中是这样,前面两个 ML 也这样。但这里情况不同。

在前面的例子中, Nil 是个 'a seq, 即空表(流)。但在这个例子中 Nil 是个 'a seqnode. cons x xq 调用 Cons (x, xq),所以 xq 应该是个 'a seq 才对。

自己做一个空表:


  1. val END = Seq (fn()=>Nil);
复制代码


再来


  1. - val x = cons 1 (cons 2 END);
  2. > val x = Seq fn : int seq
复制代码


这次好像对了,但如何把数据提取出来呢?这比前两个例子要稍微罗唆一点:


  1. exception Empty

  2. fun fst (Cons (x, _)) = x
  3.   | fst Nil = raise Empty;

  4. fun rst (Cons (_, y)) = y
  5.   | rst Nil = raise Empty;

  6. fun car (Seq(f)) = fst (f());

  7. fun cdr (Seq(f)) = rst (f());
复制代码


fst, rst 是处理 'a seqnode 的,而 car, cdr 则是处理 'a seq 的。

效果:


  1. - car x;
  2. > val it = 1 : int

  3. - (car o cdr) x;
  4. > val it = 2 : int

  5. - (car o cdr o cdr) x;
  6. ! Uncaught exception:
  7. ! Empty
  8. -
复制代码


form 和 take 和自然数集


  1. fun from k = Seq(fn()=> (Cons (k, (from (k+1)))));

  2. val N = from 0;

  3. fun take xq 0 = []
  4.   | take xq n = (car xq)::(take (cdr xq) (n-1))
  5.     handle Empty => raise Subscript;
复制代码


  1. - take N 100;
  2. > val it =
  3.     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
  4.      21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
  5.      39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
  6.      57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
  7.      75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92,
  8.      93, 94, 95, 96, 97, 98, 99] : int list
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP