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组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
随机推荐
- MyEclipse部署WebLogic
====================================================================================
- FXAA,FSAA与MSAA有什么区别?效果和性能上哪个好
而MSAA基本上只对画面中物体的边缘进行放大.混合的抗锯操作,因为边缘是锯齿最明显的地方(注意不是所有的边缘).提取边缘,主要是结合深度技术.MSAA是种硬件AA.我们一般说的4x.8x,就是放大倍数 ...
- .Net Core Md5加密整理
一..Net Core中Md5使用说明 .Net Core中自带Md5加密处理,使用方法和 .Net Framework中相同 所在命名空间 using System.Security.Cryptog ...
- 奇怪吸引子---QiChen
奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...
- Android GUI之View测量
在上篇文章(http://www.cnblogs.com/jerehedu/p/4607599.html#gui)中,根据源码探索了View的绘制过程,过程有三个主要步骤,分别为测量.布局.绘制.系统 ...
- MySQL查看数据库表容量大小
本文介绍MySQL查看数据库表容量大小的命令语句,提供完整查询语句及实例,方便大家学习使用. 1.查看所有数据库容量大小 select table_schema as '数据库', sum(table ...
- android highcharts 柱状图例子
android提供achartengine api 只能做简单的,如果是复杂的图表,个人的想法结合highcharts来完成:减小工作量,官方提供的例子也非常丰富. 通过android webview ...
- 用MATLAB生成模糊控制离线查询表
实时采样得到的数据经过模糊化处理后输入机器,通过查询模糊规则表便可得到应有的输出模糊量,从而避免了近似推理过程.实际应用中,特别是在控制系统较为简单而采用单片机控制时,常常采用这种查表法. 模糊控制表 ...
- void android.graphics.Bitmap.recycle()
void android.graphics.Bitmap.recycle() Free up the memory associated with this bitmap's pixels, and ...
- Rplidar学习(五)—— rplidar使用cartographer_ros进行地图云生成
一.Cartographer简介 Cartographer是google开源的通用2D和3D定位与地图同步构建的SLAM工具,并提供ROS接口.官网地址:https://github.com/goog ...