TSPL学习笔记(1)
扩展语法(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->grade和gpa其中gpa->grade是一个过程gpa是一个扩展语法定义.
(import (grades))
(gpa c a c b b) -> 2.8
(gpa->grade 2.8) -> b
通过import导入库之后就可以引用那个库中导出标识符了.
TSPL学习笔记(1)的更多相关文章
- TSPL学习笔记(4):数组相关练习
最近研究函数式编程,都是haskell和scheme交互着看的,所以笔记中两种语言的内容都有,练习一般也都用两种语言分别实现. 本篇练习一些数组有关的问题,之所以与数组相关是因为在命令式编程中以下问题 ...
- TSPL学习笔记(3):排序算法练习
快速排序 快排的详细介绍见,简单的说就是取输入序列中的首元素m,然后将除首元素m以外的其它元素分成两组,小于等于m的一组和大于m的一组.将3组元素组合成输入队列:小于等于m + m + 大于m. 下面 ...
- TSPL学习笔记(2):过程和变量绑定
变量的引用 语法: variable 返回: variable的值 如果在某个范围内存在对某个标识符的变量绑定,那么当这个标识符以表达式的形式出现的时候被认为是其所绑定变量的值. 在引用一个标识符的时 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
- 2014年暑假c#学习笔记目录
2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
随机推荐
- 探讨后端选型中不同语言及对应的Web框架
在进行后端选型的时候,实际上我们要选择的是一个框架.后端领域所使用的技术和框架已经趋于稳定,我们只需要按我们的需要选择所需要的框架.当存在多个框架适合时,我们再选择适合的语言.不得不指出的是,当我们喜 ...
- GoDaddy账户间域名转移PUSH以及ACCEPT接受域名过户方法
GoDaddy账户之间的域名进行过户PUSH.以及接受ACCEPT一般发生在我们有要求代购.交易域名账户之间的处理.一般的海外域名注册商账户之间是直接可以用户交易过户的,不需要经过商家允许,但是不同的 ...
- 三大分析法——SWOT、PEST、波特五力
SWOT分析法 「SWOT分析模型简介」 (也称TOWS分析法.道斯矩阵).在现在的战略规划报告里,SWOT分析应该算是一个众所周知的工具.来自于麦肯锡咨询公司的SWOT分析,包括分析企业的优势(St ...
- canvas应用——将方形图片处理为圆形
上段时间在项目中需要将方形图片处理为圆形图片,你可能会说直接用css设置border-radius: 50%就可以了,但是项目中还要将此图片的圆形图片作为一部分利用canvas将其绘制到一张背景图上面 ...
- 你真的会用Gson吗?Gson使用指南(3)
原文出处: 怪盗kidou 注:此系列基于Gson 2.4. 本次的主要内容: 字段过滤的几种方法 基于@Expose注解 基于版本 基于访问修饰符 基于策略(作者最常用) POJO与JSON的字段映 ...
- Convolutional Neural Networks: Step by Step
Andrew Ng deeplearning courese-4:Convolutional Neural Network Convolutional Neural Networks: Step by ...
- plsql连接oracle数据库,不用配置任何东西(转)
在软件开发的过程中,对于使用oracle的朋友们来说,使用plsql工具操作oracle数据库是非常方便的,可是plsql连接oracle数据库的方式有很多种,今天就给大家介绍一种最简单的连接方式,只 ...
- C++11中的右值引用及move语义编程
C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右 ...
- ubuntu下解压文件命令大全(转)
ubuntu 下rar解压工具安装方法: 压缩功能 安装 sudo apt-get install rar卸载 sudo apt-get remove rar 解压功能 安装 sudo apt-get ...
- MVC项目实践,在三层架构下实现SportsStore-11,使用Knockout实现增删改查
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...