Week 2
ML变量绑定和表达式 ML Variable Bindings and Expressions
- 注释:括号内加星号构成
(* ... *)
- 创建变量:
var x = 34;
,实现了一个整型变量对x的变量绑定,绑定的变量可以被复用 - 使用文件:
use “first.sml”;
- 复用既有绑定的变量绑定将会根据动态环境中的既有绑定值判断绑定结果
- ML具有类型检查系统,称之为静态环境,在程序执行前,会对各个变量进行类型检查
- if条件判断语句:
if ... then ... else ...;
,注意then和else后的语句只会被评估其中一个,同时要求if的静态类型必须为bool,then和else的静态类型需要一致 - 变量绑定语法:
val x = e;
- 关键字
val
,和符号=
以及;
- 变量
x
- 表达式
e
,可能有多种形式
- 关键字
- 语法(Syntax)是指书写代码的标准和方式
- 语义(Semantic)是指书写内容的含义
- 类型检查(程序运行前)
- 评估(程序运行时)
- 对于变量绑定
- 类型检查同时扩展静态环境
- 评估表达式同时扩展动态环境
表达式规则 Rules for Expressions
- 子表达式可以继续嵌套子表达式,因此表达式可以任意大
- 对于每一种表达式,都需要
- 语法
- 类型检查规则(产生一个结果类型)
- 评估规则(产生结果)
- 相对比地,对变量,有
- 语法:字符、数字或者下划线的序列,不可以数字开头
- 类型检查(使用时):查询静态环境
- 评估:查询动态环境
- 加法,有
- 语法:
e1 + e2
,其中e1
和e2
分别为表达式 - 类型检查:(对目前而言)如果两个表达式的类型都是
int
,那么加法的结果类型也是int
,如果类型不匹配,则未通过类型检查,结果无类型 - 评估:两个表达式结果的和
- 语法:
- 值
- 表达式的结果是值
- 所有的值都对应着表达式,但不是所有的表达式都是值
- 每一个值都评估为“自身”,没有评估步骤(0步)
- 条件表达式
- 语法:
if e1 then e2 else e3;
,其中if
,then
和else
是关键字e1
,e2
和e3
是子表达式 - 类型检查:
e1
必须是bool
类型,但e2
和e3
必须是相同类型t
(具体类型无限制),整个表达式的类型也会是t
- 评估:首先评估
e1
的结果v1
,如果为true
,则评估e2
的结果v2
并作为整个表达式的结果;如果为false
,则评估e3
的结果v3
并作为整个表达式的结果
- 语法:
- 小于比较表达式(自行完成)
- 语法:
e1 < e2
,其中e1
和e2
分别为表达式 - 类型检查:两个表达式的类型需要相同,结果类型为
bool
- 评估:分别评估两个表达式的结果
v1
和v2
,然后判断v1 < v2
,若为真则结果为true
,否则结果为false
- 语法:
REPL和错误 REPL and Errors
- use
use “foo.sml”;
一个非平常的表达式- 一次性将
foo.sml
中的所有绑定输入到环境中 - 结果
()
被绑定到变量it
,可以忽略
- REPL
- Read-Eval-Print-Loop
- 视为一个运行程序的简单方式
- 为了避免未知错误,重新
use
一个文件之前,应重启REPL会话
- 错误
- 语法错误
- 类型检查错误
- 评估错误(产生错误结果、产生异常或者死循环)
重影 Shadowing
- 重影:在尝试向环境中添加一个变量时,这个变量已经存在的情况
- 当根据既有绑定创建一个新的变量时,这个变量的实际值将会被录入动态环境,此时这个新变量的值和用于获取这个值的表达式或者既有绑定没有任何关系(同时,ML没有赋值操作)
- 当我们有
val a = 10;
后再有val a = 5;
这个动作并没有对a
重新赋值,事实上ML并没有改变之前环境的绑定关系,而是在不同环境的一个不同的绑定映射,对原来的绑定产生了重影
非正式函数 Functions Informally
- 函数:获得参数,计算并输出结果
fun pow (x : int, y : int) = <expression>...
- 调用为
pow(x,y);
- 函数的参数可以是表达式,甚至是嵌套调用
- 我们不需要写明函数结果类型,但是ML可以自行从函数体中推断出结果类型
- 函数可以递归调用
- 函数中的内容不可以引用在函数之后产生的绑定(相互调用了咋整?)
- 递归要比循环更加清晰和好用
正式函数 Functions Formally
- 函数:
- 语法:
fun x0 (x1 : t1, ... , xn : tn) = e
- 评估:一个函数就是一个值(声明时,
x0
已经加入环境),知道调用函数时,才会评估函数体 - 类型检查:确保函数体类型检查正确后,向函数添加
x0:(t1 * ... * tn) -> t
。对函数题,检查e
,此时静态环境包括之前所有的绑定,参数以及函数名(递归)
- 语法:
- 关于函数的类型检查:
- 新的类型
(t1 * ... * tn) -> t
,右侧时结果类型,整个结果会赋予x0
- 参数只能在
e
中使用 - 评估调用时,
x0
的结果类型就是e
的类型
- 新的类型
- 函数调用:
- 语法:
e0 (e1, ... en);
,如果只有一个参数,括号可以省略 - 类型检查:
e0
具有类型(t1 * ... * tn) -> t
,e1
具有类型t1
,……,en
具有类型tn
,那么e0 (e1, ... en);
具有类型t
- 评估:
- 在当前动态环境下,评估
e0
为一个函数fun x0 (x1 : t1, ... , xn : tn) = e
- 在当前的动态环境下,评估参数为
v1, ..., vn
- 结果来自于对
e
的评估,其环境为x1->v1, ..., xn->vn
的映射
- 在当前动态环境下,评估
- 语法:
对和其他元组 Pairs and Other Tuples
- 元组(Tuple):固定数量个不同的数据项
- 对(pair,二元组)的创建:
- 语法:
(e1, e2)
- 评估:评估
e1
到值v1
,评估e2
到值v2
,结果是(v1, v2)
- 类型检查:如果
e1
类型为ta
,e2
类型为tb
,那么对表达式的类型为ta * tb
- 语法:
- 对的访问:
- 语法:
#1 e
以及#2 e
- 评估:评估
e
并返回第一个或者第二个部分 - 类型检查:如果
e
有类型ta * tb
,那么#1 e
有类型ta
,#2 e
有类型tb
- 语法:
- 实际上,tuple是可以拥有超过两个元素的,相关的操作方式即对上述内容的泛化
- 对和元组可以被嵌套,因此上述操作也是可以被嵌套的
列表 List
- 元组可以嵌套并容纳各种数据,但是其大小必须是固定且已知的
- 列表(List)可以扩展到任意个元素,但是限制为同一类型
- 列表的创建
- 空列表:
[]
- 一般的列表:
[v1, v2, ..., vn]
- cons连接:
e1
的值为v
,e2
的值为[v1, v2, ..., vn]
,那么e1 :: e2
的值为[v, v1, v2, ..., vn]
,注意类型,v
的类型必须和vi
是一致的,而不是e2
的类型
- 空列表:
- 列表的访问,这里有三个标准库函数
null e
评估为true
当且仅当e
评估为[]
- 如果
e
评估为[v1, v2, ..., vn]
,那么hd e
评估为v1
,对空列表将产生异常 - 如果
e
评估为[v1, v2, ..., vn]
,那么tl e
评估为[v2, ..., vn]
,对空列表将产生异常,注意返回值为去除第一个元素的列表(有可能结果为空列表)
- 对列表操作的类型检查
- 对类型
t
,类型t list
表示用于描述存储该类型的列表 - 类型表示(列表与元组)可以嵌套
[]
是一个空列表,其类型被表示为'a list
,表示为alpha列表,同时可以作为任何类型的列表做cons连接null
类型为'a list -> bool
hd
类型为'a list -> 'a
tl
类型为'a list -> 'a list
- 对类型
- 通常对列表的操作会经常使用递归
let表达式 Let Expressions
- let表达式
- 语法:
let b1 b2 ... bn in e end
,bi
是任意的绑定,e
是任意的表达式,是let表达式的表达体 - 类型检查:按顺序检查每一个
bi
和e
的类型,前面的类型可以用于后面的类型判定,e
的类型将是整个表达式的类型 - 评估:按顺序评估每一个
bi
和e
在一个包含了之前绑定的动态环境中(后面的可以利用前面的);e
的结果是整个let表达式的结果,所有上面的绑定将在表达体中可用,但在外部不可用
- 语法:
- 给出了一种作用域(scope)的想法,let后面的绑定只在in中可用,在in中可能存在对顶级绑定的重影的情形(局部变量)
- 像普通表达式一样,let表达式可以被放在任何地方,并且可以嵌套
嵌套函数 Nested Functions
- 我们可以在let表达式中定义函数
- 当我们需要一个局部函数的时候,就可以向定义局部变量一个在let表达式中定义一个局部函数
- 使用嵌套函数的时机
- 确保定义的这个“局部函数”确实只在特定的函数中使用
- 处处可用可能产生滥用问题,作用域可以限制这类问题的出现
- 可能会被改变或移除的函数,对其限制适用范围可以最小化影响
let与效率 Let and Efficiency
- 尽量避免重复地递归
选项 Options
t option
式一个对于任意类型t
的类型- 创建:
NONE
拥有类型'a option
SOME e
,如果e
拥有类型t
,那么有类型t option
(类比于e :: []
结果的类型)
- option不是某种类型,而是保持某种类型的类型
- 访问:
isSome
拥有类型'a option -> bool
,对SOME
返回true
,对NONE
返回false
valOf
拥有类型'a option -> 'a
,将option中的元素拿出来,对NONE
,将产生异常
布尔值与比较运算 Booleans and Comparison Operations
-
e1 andalso e2
- 类型检查:
e1
和e2
必须为bool
- 评估:
e1
的结果如果是false
,结果为false
,否则取决于e2
的结果
- 类型检查:
-
e1 orelse e2
- 类型检查:同上
- 评估:
e1
的结果如果是true
,结果为true
,否则取决于e2
的结果
-
not e1
- 类型检查:同上
- 评估:
e1
的结果如果是true
,结果为false
,反之亦然
-
前二者是运算符,后一者为函数
-
上述三种运算关系都可以使用
if
实现 -
对
int
类型的比较,有:= <> > < >= <=
-
上述后四个比较运算符可用于
real
类型,但是不能混用
不异变 No Mutation
- ML中,大多数数据类型不允许异变
- 想要修改一个值,只能重新创建而非修改内容
- 在ML中,允许出现别名的情况,但是由于不会出现“异变”的情形,因此不必考虑因为其中一个引用修改内容导致另外一个引用所指向的值发生改变的情形
总结
- 语法:如何写下语言的结构
- 语义:程序的意思是什么(评估规则)
- 惯用语:对于语言中某些特性使用的典型模式
- 库:语言提供的标准设施
- 工具:语言的实现提供的用于简化工作的内容(实际上独立于语言的部分)