programming-languages学习笔记--第2部分
programming-languages学习笔记–第2部分
*/-->
pre.src {background-color: #292b2e; color: #b2b2b2;}
pre.src {background-color: #292b2e; color: #b2b2b2;}
pre.src {background-color: #292b2e; color: #b2b2b2;}
programming-languages学习笔记–第2部分
目录
1 自定义类型
- 基本类型 (int bool unit char)
- 复合类型 (tuples, lists, options)
构造复合类型:
- each of: 组合所有 例如:tuples, (int * bool) 包含一个int和一个bool, 与java中带的字段类相似
- one of: 选择一个 例如:option, int option可以包含一个int或者空,java中可以通过子类化实现。
- self reference: 自引用类型,用于构造递归数据类型,例如列表和树
list使用了全部3种构造
2 Records
属于"each-of"类型:
- record的值用字段来保存
- record的类型也用字段保存
- 字段的顺序无关紧要
与tuple相比更容易记忆(有字段名),
注意代码中-开始的行是repl的输入,下面是输出。
- val x = {bar = (+, true andalso true), foo = +, baz = (false,)};
val x = {bar=(,true),baz=(false,),foo=}
: {bar:int * bool, baz:bool * int, foo:int}
- #bar x;
val it = (,true) : int * bool
- #foo x;
val it = : int
ML中不需要定义record类型,直接写record表达式,类型检查器会给出正确的类型。
3 tuples as syntactic sugar
records和tuples很相似。都是"each-of"构造。唯一的不同是records通过名称访问,tuples通过位置访问。
java的方法调用组合了这两种方式:方法内使用变量名访问不同的参数,但调用者通过位置传递参数。
可以通过构造records来定义tuples:
- - val a_pair = (+, +);
val a_pair = (,) : int * int - - val a_record = {second=+, first=+};
val a_record = {first=,second=} : {first:int, second:int} - - val another_pair = {=, =};
val another_pair = (,) : int * int - - # a_pair + # another_pair;
val it = : int - - val x = {="hi", =true};
val x = {=true,="hi"} : {:bool, :string} - - val y = {="hi", =true, =+};
val y = (true,,"hi") : bool * int * string
实际上,tuple就是record, tuple是record的语法糖
方便设计实现(语言核心足够简单),方便理解
4 datatype bindings
datatypee mytype = TwoInts of int * int
| Str of string
| Pizza
每个mytype的值都从一个构造器产生,
每个值包括:构造的tag, 对应的数据
上面的示例创建一个新类型 mytype 和3个构造器 TwoInts, Str, Pizza
构造器两种表示,它可以是创建新类型值的函数(如果有 of t ),或者是新类型的值。
上面的示例中, TwoInts 是一个函数,类型为 int\*int -> mytype, Str 是一个类型为 string->mytype 的函数, Pizza 是类型为 mytype 的值。
要访问datatype的值,需要:
- 检查是哪个构造器构造的
- 提取数据
例如: null 和 isSome检查;
ht,tl 和 valOf 提取数据
- - datatype exp = Constant of int
| Negate of exp
| Add of exp * exp
| Multiply of exp * exp ;
datatype exp
= Add of exp * exp | Constant of int | Multiply of exp * exp | Negate of exp - - val r = Add (Constant ( + ), Negate (Constant ));
val r = Add (Constant ,Negate (Constant )) : exp - - fun eval e =
case e of
Constant i => i
| Negate e2 => ~ (eval e2)
| Add(e1, e2) => (eval e1) + (eval e2)
| Multiply(e1,e2) => (eval e1) * (eval e2)
;
= val eval = fn : exp -> int - - eval r;
val it = : int
5 case 表达式
ML使用case表达式和模式匹配访问"one-of"值
- - datatype mytype = TwoInts of int * int
| Str of string
| Pizza
;
= datatype mytype = Pizza | Str of string | TwoInts of int * int - fun f (x : mytype) =
case x of
Pizza =>
| Str s =>
| TwoInts(i1, i2) => i1 + i2
;
= val f = fn : mytype -> int - - f Pizza;
val it = : int - - f (TwoInts (, ));
val it = : int
在某种意义上,case表达式就像一个更强大的if-then-else表达式。它首先求值 case 和 of 中间的表达式,然后求值第一个匹配的分支的表达式。和条件表达式一样,每个分支表达式必须有相同的类型,作为case表达式的值。
对于每个分支 p => e p是一个模式,e是一个表达式;使用|分割多个分支。模式看起来像表达式,但不要把它们想做表达式。它们用来匹配case的第一个表达式( case 后面的部分)的值。这就是为什么case表达式叫做模式匹配。
6 类型同义词
type aname = t
两个名字可以互换使用
- datatype suit = Club | Diamond | Heart | Spade ;
datatype suit = Club | Diamond | Heart | Spade - datatype rank = Jack | Queen | King | Ace | Num of int;
datatype rank = Ace | Jack | King | Num of int | Queen - type card = suit * rank;
type card = suit * rank - type name_record = { student_num : int option,
first : string,
middle : string option,
last : string };
type name_record =
{first:string, last:string, middle:string option, student_num:int option} - fun is_Queen_of_Spades (c : card) =
# c = Spade andalso # c = Queen;
val is_Queen_of_Spades = fn : card -> bool - val c1 : card = (Diamond, Ace);
val c1 = (Diamond,Ace) : card - val c2 : suit * rank = (Heart, Ace);
val c2 = (Heart,Ace) : suit * rank - val c3 = (Spade, Ace);
val c3 = (Spade,Ace) : suit * rank - is_Queen_of_Spades c1;
val it = false : bool - is_Queen_of_Spades c2;
val it = false : bool - is_Queen_of_Spades c3;
val it = false : bool
7 Lists和Options也是datatype
因为datatype可以递归定义,我们可以用来定义自己的lists类型:
- datatype my_int_list = Empty
| Cons of int * my_int_list ;
= datatype my_int_list = Cons of int * my_int_list | Empty - val one_two_three = Cons(, Cons(, Cons(, Empty)));
val one_two_three = Cons (,Cons (,Cons #)) : my_int_list - fun append_mylist (xs, ys) =
case xs of
Empty => ys
| Cons(x, xs') => Cons(x, append_mylist(xs', ys));
= val append_mylist = fn : my_int_list * my_int_list -> my_int_list
对于options, SOME 和 NONE 是构造器。
对于lists来说 [] 和 :: 都是构造器。::有点特殊,因为它是中缀操作符(在两个操作数中间)。
- fun inc_or_zero intoption =
case intoption of
NONE =>
| SOME i => i + ;
= val inc_or_zero = fn : int option -> int - fun append(xs, ys) =
case xs of
[] => ys
| x::xs' => x :: append(xs', ys) ;
= val append = fn : 'a list * 'a list -> 'a list
模式匹配的优点: 不会有例外情况。不能应用错误的函数。
8 多态数据类型
好的语言设计:定义自己的多态类型
datatype 'a option = NONE | SOME of 'a datatype 'a mylist = Empty | Cons of 'a * 'a mylist datatype ('a, 'b) tree =
Node of 'a * ('a, 'b) tree * ('a, 'b) tree
| Leaf of 'b
9 Each-of类型的模式匹配:val绑定的真相
val绑定模式,val绑定可以使用模式, val p = e, 例如:
- val (x,y) = (,);
val x = : int
val y = : int - val {f1=a, f2=b} = {f2 =, f1=};
val a = : int
val b = : int
当知道一个模式肯定会被匹配时,使用模式匹配就是为了提取值。
- fun full_name (r : {first:string, middle:string, last:string}) =
let val {first=x, middle=y, last=z} = r
in
x ^ " " ^ y ^ " " ^ z
end
val full_name = fn : {first:string, last:string, middle:string} -> string - fun sum_triple (triple : int*int*int) =
let val (x,y,z) = triple
in
x + y + z
end
val sum_triple = fn : int * int * int -> int (* 在定义函数绑定中使用模式 *)
- fun full_name {first=x, middle=y, last=z} = x ^ " " ^ y ^ " " ^ z
= val full_name = fn : {first:string, last:string, middle:string} -> string - full_name {first="a", middle="b", last="c"};
val it = "a b c" : string - fun sum_triple (x,y,z) = x + y + z
= val sum_triple = fn : int * int * int -> int
ML中的所有函数都是一个参数,按照模式匹配展开,可以是tuple:(a,b,c);也可以是record:{a,b,c}或者其它。
这种灵活性很有用,可以把函数的返回值直接传递给其它有多个参数的函数。没有无参数的函数,hello()也是一个参数(空的tuple, unit 类型)。因为存在预定义的类型 , datatype unit = ()
10 类型推断,多态类型与相等类型
在ML中,所有的变量和函数都有一个类型,类型推断只是表示不需要把类型写下来。
类型推断有时会让你的函数更通用。
多态表示更通用的类型,例如append的类型 'a list * 'a list -> 'a list ,可以统一地把'a替换为 string,就像append具有 string list * string list -> string list 类型一样使用。可以用任何类型替换'a。
''a 表示相等类型:
- fun same_thing(x,y) = if x=y then "yes" else "no";
stdIn:1.28 Warning: calling polyEqual
val same_thing = fn : ''a * ''a -> string
11 嵌套模式
模式是递归的。通常模式匹配就是取一个值和一个模式,然后确定模式是否与值匹配,如果匹配,变量绑定到值的正确部分。模式匹配的递归定义的关键点:
- 一个变量模式(x)匹配任意值v并引入一个绑定
- 模式 C 匹配值 C ,如果 C 是一个没有任何数据的构造器
- 模式 C p (构造器 C 和 模式 p )匹配一个值 C v (注意构造器相同),如果 p 匹配 v (嵌套模式匹配携带的值)。它引入了 p 匹配 v 的绑定。
- 模式(p1,p2,…,pn)匹配tuple值(v1,v2,…,vn),如果p1匹配v1,p2匹配v2,…,pn匹配vn.它引入所有递归匹配引入的绑定。
- record模式与tuple类似 {f1=p1,…,fn=pn}
模式匹配中使用通配符 _ 匹配所有值,但不会引入新绑定。
- exception BadTriple
exception BadTriple - fun zip list_triple =
case list_triple of
([],[],[]) => []
| (hd1::tl1, hd2::tl2, hd3::tl3) => (hd1,hd2,hd3)::zip(tl1,tl2,tl3)
| _ => raise BadTriple
val zip = fn : 'a list * 'b list * 'c list -> ('a * 'b * 'c) list - fun unzip3 lst =
case lst of
[] => ([],[],[])
| (a,b,c)::tl => let val (l1,l2,l3) = unzip3 tl
in
(a::l1, b::l2, c::l3)
end
val unzip3 = fn : ('a * 'b * 'c) list -> 'a list * 'b list * 'c list - fun nondecreasing intlist =
case intlist of
[] => true
| _::[] => true
| head::(neck::rest) => (head <= neck andalso nondecreasing (neck::rest))
val nondecreasing = fn : int list -> bool - datatype sgn = P | N | Z
= datatype sgn = N | P | Z - fun multsign (x1,x2) =
let fun sign x = if x= then Z else if x> then P else N
in
case (sign x1,sign x2) of
(Z, _) => Z
| (_, Z) => Z
| (P, P) => P
| (N, N) => N
| _ => N
end
= val multsign = fn : int * int -> sgn
12 异常
ML有内置的异常概念。使用 raise 抛出一个异常。使用异常绑定创建自己的异常。
异常构造器可以创建 exn 类型的值。
- exception MyUndesirableCondition
- exception MyOtherException of int * int - fun maxlist (xs, ex) =
case xs of
[] => raise ex
| x::[] => x
| x::xs' => Int.max(x,maxlist(xs',ex));
val maxlist = fn : int list * exn -> int
处理异常使用handle表达式: e1 handle p => e2 , e1和e2是表达式,p是用来匹配异常的模式。
13 尾递归和累加器
新的编程模型:尾递归,编写有效率的递归函数。使用累加器把一些函数变为尾递归。
- fun sum1 xs =
case xs of
[] =>
| i::xs' => i + sum1 xs'
val sum1 = fn : int list -> int - fun sum2 xs =
let fun f (xs,acc) =
case xs of
[] => acc
| i::xs' => f(xs',i+acc)
in
f(xs,)
end
val sum2 = fn : int list -> int
函数调用的实现依靠调用栈(call stack),调用栈的内容是每个函数为一个元素,这个函数是已启动但还没有完毕的调用。
每个元素保存局部变量和函数还未求值的部分。当一个函数体内调用了另一个函数,一个新的元素push到调用栈,当被调用的函数完成后弹出。
对于 sum1 ,每个 sum1 递归调用都会产生一个调用栈元素,栈会和列表一样大。因为在弹出每个栈帧后,调用者要加 i 到每个递归结果并返回。
对于 sum2 ,在被调用函数返回后,调用函数不需要做任何事,只需返回被调用函数的结果。这种情况叫做尾递归,函数式语言通常会对这种情况优化:当调用一个尾递归调用,调用者的栈帧在调用之前弹出,被调用者的栈帧替换调用者的。这很简单:调用者只是返回被调用者的结果。因此,调用 sum2 只需要1个栈帧。
使用累加器是把递归函数转换为尾递归函数的常用方法。通常转换一个非尾递归函数到尾递归函数需要满足结合律。
如果一个调用在尾部位置,它就是尾递归调用。尾部位置定义如下:
- 在 fun f(x) = e 中, e 在尾部位置
- 如果一个表达式不在尾部位置,那么它的子表达式都不在尾部位置
- 如果 if e1 then e2 else e3 在尾部位置,则 e2 和 e3 在尾部位置( e1 不在),case表达式类似。
- 如果 let b1 … bn in e end 在尾部位置, 则 e 在尾部位置(但绑定中的表达式不在)。
- 函数调用参数不在尾部位置。
作者: ntestoc
Created: 2018-12-12 Wed 13:44
programming-languages学习笔记--第2部分的更多相关文章
- CUDA Programming Guide 学习笔记
CUDA学习笔记 GPU架构 GPU围绕流式多处理器(SM)的可扩展阵列搭建,每个GPU有多个SM,每个SM支持数百个线程并发执行.目前Nvidia推出了6种GPU架构(按时间顺序,详见下图):Fer ...
- Programming Erlang 学习笔记(一)
入门 启动Shell 在cmd中输入命令”erl”,百分号(%)表示一个注释的开始,从百分号开始到这行结束的所有文本都被看做是注释. 一个完整的命令需要以一个句点和一个回车结束. 退出erlang的命 ...
- UIView Programming Guide学习笔记
|View |Creating and Configuring View Objects |Creating and Managing a View Hierarchy |Adjusting the ...
- The C++ Programming Language 学习笔记 第7章 函数
1.关于内联函数(inline) 借用一下书中的例子. inline int fac(int n) { ) ? :n*fac(n-); } inline描述符给编译器一个提示,要求 ...
- The C++ Programming Language 学习笔记 第6章 表达式和语句
1.关于strcpy函数. 书中说c风格的字符串尽量少用,strcpy这样的函数应该也要少用.这里讲这个函数主要是要通过本章课后练习第十题来讲一下前面提及的要点.巩固一下前几章的知识.写了一段,本来感 ...
- The C++ Programming Language 学习笔记 第5章 指针、数组和结构
1.关于输出指向字符的指针的值. 现在定义,char c='a',char* pc=&c.在C中,输出该值只需要printf("%p\n",pc);而在C++中,如果cou ...
- The C++ Programming Language 学习笔记 第四章 类型和声明
1.关于main 函数中的 return 0 C99标准中,main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统).如果 main 函数的最后没有写 return ...
- 3D Game Programming withDX11 学习笔记(一) 数学知识总结
在图形学中,数学是不可或缺的一部分,所以本书最开始的部分就是数学知识的复习.在图形学中,最常用的是矢量和矩阵,所以我根据前面三个章节的数学知识,总结一下数学知识. 一.矢量 数学中的矢量,拥有方向和长 ...
- Learning ROS for Robotics Programming Second Edition学习笔记(十) indigo Gazebo rviz slam navigation
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 moveit是书的最后一章,由于对机械臂完全不知,看不懂 ...
- Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...
随机推荐
- 在centos linux上安装docker
前置条件 64-bit 系统 kernel 3.10+ 1.检查内核版本,返回的值大于3.10即可. $ uname -r 2.确保yum是最新的 $ yum update 3.安装 Docker y ...
- 压缩图片或pdf
压缩图片或pdf { /// <summary> /// 压缩图片或pdf大小的Level /// </summary> public enum ReduceSizeLevel ...
- C#语言-07.文件操作
a. 文件操作:适用于相对简单的数据保存 i. 读写文件的步骤: . 创建文件流 . 创建读写器 . 读写文件 . 关闭读写器 . 关闭文件流 ii. FileStream(文件流),它主要用于读写文 ...
- 用 Redis Desktop Manager 远程连接 redis 数据库。
环境: 本机OS:window 10(本机没有安装redis) redis 服务器:centos 7 使用 Redis Desktop Manager 工具远程连接 redis. Redis Desk ...
- Java基础(十一)集合框架
一.集合框架 1.集合框架定义 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常 ...
- Java 异常的处理方式--throws和try catch
异常的第一种处理方式throws. 看以下例子: import java.io.*;public class ExceptionTest04{ public static void main(Stri ...
- var a =10 与 a = 10的区别
学习文章------汤姆大叔-变量对象 总结笔记 变量特点: ①变量声明可以存储在变量对象中.②变量不能直接用delete删除. var a =10 与 a = 10的区别: ①a = 10只是为全局 ...
- Eclipse常用操作
一 Eclipse常用快捷键 ctrl+Q:跳到最后一次编辑处. ctrl+F:在当前文件中查找并替换. ctrl+T:查看类的继承关系. alt+左箭头:返回到光标的上一个位置. alt+右箭头:前 ...
- 【阿里云产品公测】ACE下上传文件永久存储实践
本帖主要内容: ;$,=VB:' 在阿里云的ACE下,我是如何实现让上传的文件永久保存的? ,%"!8T 本文以PHP为例,具体知识点如下: WD# 96V 第一,扩展服务“存储 ...
- 微信小程序开发2-第一个小程序开发准备
1.首先在官网上注册一个账号( https://mp.weixin.qq.com/ )申请一个AppID(类似于人的身份证,小程序也需要身份证) 注册过程不多说 2.安装开发工具( https://m ...