TSPL学习笔记(1)

时间:2023-02-18 20:24:55

扩展语法(Syntactic extensions)

扩展语法就是通过核心语法或已经定义的扩展语法创建一种新的语法模式.

Scheme核心语法模式包括:
  • 顶层定义
  • 常量
  • 变量
  • 过程应用
  • '(quote)表达式
  • lambda表达式
  • if表达式
  • set!表达式
Scheme语法:
<program>    		  --> <form>*
<form> --> <definition> | <expression>
<definition> --> <variable definition> | (begin <definition>* )
<variable definition> --> (define <variable> <expression> )
<expression> --> <constant>
| <variable>
| (quote <datum> )
| (lambda <formals> <expression> <expression>* )
| (set! <variable> <expression> )
| <application>
<constant> --> <boolean> | <number> | <character> | <string>
<formals> --> <variable>
| ( <variable>* )
| ( <variable> <variable>* . <variable> )
<application> --> ( <expression> <expression>* )

扩展语法let的定义:

(define-syntax let
(syntax-rules ()
[(_ ((x e) ...) b1 b2 ...)
((lambda (x ...) b1 b2 ...) e ...)]))

define-syntax之后出现的名字是新定义的扩展语法关键字,在这里是let.

之后通过syntax-rules定义转换规则:syntax-rules后紧跟的是额外关键字的list,在这里是个空的list.

后面是一系列的规则(通过[]定义一个规则),由模式/模板对组成.在let的定义中只使用了一个规则.其中模式部分定义了语法扩展的输入模式.

模板部分定义了如何将输入模式转换成输出模式.

规则中的模式部分必须是一个结构完整的表达式,通常第一个元素是_.如果一个扩展语法的定义中出现了多个规则

,解释器/编译器会按规则定义的顺序去匹配模式,如没有合适匹配将会导致一个语法错误.

letrec

通过let将一个名字与一个lambda表达绑定之后,是无法在lambda表达式内调用绑定的名字以实现递归调用的,例如:

(let ([sum (lambda (ls)
(if (null? ls)
0
(+ (car ls) (sum (cdr ls)))))])
(sum '(1 2 3 4 5)))

将会报错,提示sum没有定义,这是因为sum只是在let表达式的body部分可见而在lambda表达式内是不可见的.

要达到上述效果必须使用扩展语法letrec

(letrec ([sum (lambda (ls)
(if (null? ls)
0
(+ (car ls) (sum (cdr ls)))))])
(sum '(1 2 3 4 5)))

letrec的语法形式如下:

(letrec ((var expr) ...) body 1  body 2  ...)

其中var不仅在body中可见,在expr中也是可见的.

使用letrec表达式的时候必须要注意一点:任意expr的求值都不应依赖任意var的求值,例如下面的表达式就违反了这一点.

(letrec ([y (+ x 2)]
[x 1])
y)

(+ x 2)的求值依赖于x的值,运行时将会提示异常: x没定义.

具名let表达式

具名let表达式语法形式如下:

(let name ((var expr) ...)
body 1 body 2 ...)

它与let表达式的唯一区别在于,在body中,name被绑定成一个过程且可以递归调用.而传递给这个过程的参数会成为var的新值,下面是一个用具名let实现的list?函数:

(define list?
(lambda (x)
(let race ([h x] [t x])
(if (pair? h)
(let ([h (cdr h)])
(if (pair? h)
(and (not (eq? h t))
(race (cdr h) (cdr t)))
(null? h)))
(null? h)))))

如同let表达式可被表示为直接将lambda表达式应用在参数上一样,具名let可被表示为将一个递归过程应用到参数上,例如

上面具名let的语法形式可以用letrec改写如下:

((letrec ((name (lambda (var ...) body 1  body 2  ...)))
name)
expr ...)

(letrec ((name (lambda (var ...) body 1  body 2  ...)))
(name expr ...))

延续(continuation)

[见 2014-09-04-延续(continuation)](https://github.com/sniperHW/sniperHW.github.io/blob/master/md/2014-09-04-延续(continuation)

延续传递风格(Continuation Passing Style)

在执行过程调用的时候实际上产生了一个隐含的延续,这个延续是被调用过程返回后后续的执行流程.例如:

(letrec ([f (lambda (x) (cons 'a x))]
[g (lambda (x) (cons 'b (f x)))]
[h (lambda (x) (g (cons 'c x)))])
(cons 'd (h '())))

调用过程(f x)的延续是将'b cons (f x)的返回值,然后返回给更上一层的调用者.

可以通过将延续打包成一个过程,作为参数传递给被调函数,然后在被调函数中执行这个过程的形式来改写上述代码:

(letrec ([f (lambda (x k3) (k3 (cons 'a x)))]
[g (lambda (x k2)
(f x (lambda (v) (k2 (cons 'b v)))))]
[h (lambda (x k1) (g (cons 'c x) k1))])
(h '() (lambda (v) (cons 'd v))))

首先看[f (lambda (x k3) (k3 (cons 'a x)))],其对应的延续是(cons 'b (f x)),此时k3是(lambda (v) (k2 (cons 'b v)))

所以(k3 (cons 'a x))等价于(k2 (cons 'b (cons 'a x))),而k2是(lambda (v) (cons 'd v))所以(k2 (cons 'b (cons 'a x)))等价于(cons 'd (cons 'b (cons 'a x))),所以在f的函数体中实际执行的是(cons 'd (cons 'b (cons 'a x))).

这种将延续打包成过程作为参数传递给被调函数,并在被调函数中执行延续的风格就叫延续传递风格.

显然第一个版本的代码比延续传递风格的代码更简单易懂,那么延续传递风格有什么作用。

首先第一版的代码不是尾递归的,而延续传递风格的版本是尾递归的,也就是说可以通过延续传递风格把非尾递归的代码改成尾递归的.

其次,Scheme中函数调用只能有一个返回值,而将延续打包成过程之后,作为过程调用的延续是可以接受多个参数的.我们甚至传递几个延续作为参数,由被调过程根据不同的情况调用不同的延续,例如:

(define integer-divide
(lambda (x y success failure)
(if (= y 0)
(failure "divide by zero")
(let ([q (quotient x y)])
(success q (- x (* q y))))))) (integer-divide 10 3 list (lambda (x) x)) (3 1)
(integer-divide 10 0 list (lambda (x) x)) "divide by zero"

内部定义

可以在lambda,let,letrec表达式body的最前面通过define表达式定义变量,这样定义的变量只在外围表达式的body内有效,

也就是一个局部变量.

例如可以将:

(letrec ([even?
(lambda (x)
(or (= x 0)
(odd? (- x 1))))]
[odd?
(lambda (x)
(and (not (= x 0))
(even? (- x 1))))])
(list (even? 20) (odd? 20))) -> (#t #f)

替换成内部定义的形式:

(let ()
(define even?
(lambda (x)
(or (= x 0)
(odd? (- x 1)))))
(define odd?
(lambda (x)
(and (not (= x 0))
(even? (- x 1)))))
(even? 20)) -> #t

内部定义与letrec的主要区别在于,内部定义中变量的定义是严格遵循从左到右求值(注:Scheme解释器会忽略换行就好像所有的代码都在一行上一样,所以出现在前面的行相当于在左边),而letrec表达式中的绑定则可以以任意顺序求值.例如在上面的示例代码的内部定义版本中even?必定先于odd?求值,而在letrec的版本则不保证这一点.

为了保证从左到右的求值顺序可以使用letrec*

(define var expr 0 )
.
.
.
expr 1
expr 2
.
.
.

等价于

(letrec* ((var expr 0 ) ...) expr 1  expr 2  ...)

等价于

(let ()
(define var expr 0 )
.
.
.
expr 1
expr 2
.
.
.
)

内部定义与letrec*的主要区别在于内部定义只能出现在表达式体的开始部分而letrec*可以出现在任何地方.另一个区别是扩展语法定义也可以是内部定义,区别于顶层扩展语法定义,内部扩展语法定义的有效性局部于定义的表达式体。

(let ([x 3])
(define-syntax set-x!
(syntax-rules ()
[(_ e) (set! x e)]))
(set-x! (+ x x))
x)->6

以上扩展语法定义中,set-x!只在let表达式内部有效.

内部定义结合顶层定义和赋值一起使用提供了一种使得程序模块化的手段.例如:

(define export-var #f)
.
.
.
(let ()
(define var expr)
.
.
.
init-expr
.
.
.
(set! export-var export-val)
.
.
.
)

通过顶层定义,export-var在全局可见.而在let中内部的定义则只能在本模块可见.

另一种提供模块化的手段是使用库:

(library (grades)
(export gpa->grade gpa)
(import (rnrs))
(define in-range?
(lambda (x n y)
(and (>= n x) (< n y))))
(define-syntax range-case
(syntax-rules (- else)
[(_ expr ((x - y) e1 e2 ...) ... [else ee1 ee2 ...])
(let ([tmp expr])
(cond
[(in-range? x tmp y) e1 e2 ...]
...
[else ee1 ee2 ...]))]
[(_ expr ((x - y) e1 e2 ...) ...)
(let ([tmp expr])
(cond
[(in-range? x tmp y) e1 e2 ...]
...))]))
(define letter->number
(lambda (x)
(case x
[(a) 4.0]
[(b) 3.0]
[(c) 2.0]
[(d) 1.0]
[(f) 0.0]
[else (assertion-violation 'grade "invalid letter grade" x)])))
(define gpa->grade
(lambda (x)
(range-case x
[(0.0 - 0.5) 'f]
[(0.5 - 1.5) 'd]
[(1.5 - 2.5) 'c]
[(2.5 - 3.5) 'b]
[else 'a])))
(define-syntax gpa
(syntax-rules ()
[(_ g1 g2 ...)
(let ([ls (map letter->number '(g1 g2 ...))])
(/ (apply + ls) (length ls)))])))

通过关键字library定义了一个名为grades的库向外导出了两个标识符gpa->gradegpa其中gpa->grade是一个过程gpa是一个扩展语法定义.

(import (grades))
(gpa c a c b b) -> 2.8
(gpa->grade 2.8) -> b

通过import导入库之后就可以引用那个库中导出标识符了.

TSPL学习笔记(1)的更多相关文章

  1. TSPL学习笔记&lpar;4&rpar;&colon;数组相关练习

    最近研究函数式编程,都是haskell和scheme交互着看的,所以笔记中两种语言的内容都有,练习一般也都用两种语言分别实现. 本篇练习一些数组有关的问题,之所以与数组相关是因为在命令式编程中以下问题 ...

  2. TSPL学习笔记&lpar;3&rpar;&colon;排序算法练习

    快速排序 快排的详细介绍见,简单的说就是取输入序列中的首元素m,然后将除首元素m以外的其它元素分成两组,小于等于m的一组和大于m的一组.将3组元素组合成输入队列:小于等于m + m + 大于m. 下面 ...

  3. TSPL学习笔记&lpar;2&rpar;&colon;过程和变量绑定

    变量的引用 语法: variable 返回: variable的值 如果在某个范围内存在对某个标识符的变量绑定,那么当这个标识符以表达式的形式出现的时候被认为是其所绑定变量的值. 在引用一个标识符的时 ...

  4. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  5. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  6. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  7. 2014年暑假c&num;学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  8. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  9. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

随机推荐

  1. C&num;事件学习

    通过代码触发事件(第10章).由对象触发的事件(Timer每隔Interval指定时间,就触发Tick事件,第8章使用Timer). 目录: 避免递归事件 访问对象的事件 使用事件参数 创建事件处理程 ...

  2. 淘宝WAP版小BUG分析

    前几天发现的一个淘宝WAP版的小BUG,就是用桌面版chrome看的时候产品评价中的图片显示不出来,都是图裂了. 这是什么原因呢?图片为什么会显示不出来呢?淘宝的技术人员.测试人员不可能没发现啊.开启 ...

  3. PHP就业班心得:IP与域名以及DNS和端口号的概念

    什么是IP地址 概念:IP地址就相当于人们的身份证号码!每一个连入Internet的计算机都应该有全世界独一无二的IP地址 IP地址是使用32个bit位来保存,也就是4个字节! 为了方便记忆,采用十进 ...

  4. Centos7下用命令同步标准时间

    新装的CentO7S系统服务器可能设置了错误的时间,需要调整时区和时间.如下是CentOS7系统使用NTP协议,从一个时间服务器同步标准时间: [root@localhost ~]# cp /usr/ ...

  5. 告别硬编码-发个获取未导出函数地址的Dll及源码

    还在为找内核未导出函数地址而苦恼嘛? 还在为硬编码通用性差而不爽吗? 还在为暴搜内核老蓝屏而痛苦吗? 请看这里: 最近老要用到内核未导出的函数及一些结构,不想再找特征码了,准备到网上找点符号文件解析的 ...

  6. iOS实现图片的缩放和居中显示

    直接上代码 // // MoveScaleImageController.h // MoveScaleImage // // Created by on 12-4-24. // Copyright ( ...

  7. POJ 3258

    River Hopscotch Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 5961   Accepted: 2579 D ...

  8. SVNclient安装与使用

    Technorati 标签: SVN SVNclient安装与使用 1 下载最新版本号1.5.2 最新版本号:TortoiseSVN-1.5.2.13595-win32-svn-1.5.1.msi 下 ...

  9. C&plus;&plus; 对象的内存布局(上)

    C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文 ...

  10. java并发编程&lpar;Exchanger&rpar;

    package org.bianqi.demo1; import java.util.concurrent.Exchanger; import java.util.concurrent.Executo ...