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的值,需要:

  1. 检查是哪个构造器构造的
  2. 提取数据

例如: 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表达式。它首先求值 caseof 中间的表达式,然后求值第一个匹配的分支的表达式。和条件表达式一样,每个分支表达式必须有相同的类型,作为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, SOMENONE 是构造器。
对于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 在尾部位置,则 e2e3 在尾部位置( e1 不在),case表达式类似。
  • 如果 let b1 … bn in e end 在尾部位置, 则 e 在尾部位置(但绑定中的表达式不在)。
  • 函数调用参数不在尾部位置。

作者: ntestoc

Created: 2018-12-12 Wed 13:44

programming-languages学习笔记--第2部分的更多相关文章

  1. CUDA Programming Guide 学习笔记

    CUDA学习笔记 GPU架构 GPU围绕流式多处理器(SM)的可扩展阵列搭建,每个GPU有多个SM,每个SM支持数百个线程并发执行.目前Nvidia推出了6种GPU架构(按时间顺序,详见下图):Fer ...

  2. Programming Erlang 学习笔记(一)

    入门 启动Shell 在cmd中输入命令”erl”,百分号(%)表示一个注释的开始,从百分号开始到这行结束的所有文本都被看做是注释. 一个完整的命令需要以一个句点和一个回车结束. 退出erlang的命 ...

  3. UIView Programming Guide学习笔记

    |View |Creating and Configuring View Objects |Creating and Managing a View Hierarchy |Adjusting the ...

  4. The C++ Programming Language 学习笔记 第7章 函数

    1.关于内联函数(inline)      借用一下书中的例子. inline int fac(int n) { ) ? :n*fac(n-); }      inline描述符给编译器一个提示,要求 ...

  5. The C++ Programming Language 学习笔记 第6章 表达式和语句

    1.关于strcpy函数. 书中说c风格的字符串尽量少用,strcpy这样的函数应该也要少用.这里讲这个函数主要是要通过本章课后练习第十题来讲一下前面提及的要点.巩固一下前几章的知识.写了一段,本来感 ...

  6. The C++ Programming Language 学习笔记 第5章 指针、数组和结构

    1.关于输出指向字符的指针的值. 现在定义,char c='a',char* pc=&c.在C中,输出该值只需要printf("%p\n",pc);而在C++中,如果cou ...

  7. The C++ Programming Language 学习笔记 第四章 类型和声明

    1.关于main 函数中的 return 0 C99标准中,main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统).如果 main 函数的最后没有写 return ...

  8. 3D Game Programming withDX11 学习笔记(一) 数学知识总结

    在图形学中,数学是不可或缺的一部分,所以本书最开始的部分就是数学知识的复习.在图形学中,最常用的是矢量和矩阵,所以我根据前面三个章节的数学知识,总结一下数学知识. 一.矢量 数学中的矢量,拥有方向和长 ...

  9. Learning ROS for Robotics Programming Second Edition学习笔记(十) indigo Gazebo rviz slam navigation

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 moveit是书的最后一章,由于对机械臂完全不知,看不懂 ...

  10. Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

随机推荐

  1. c#中的程序集

     程序集:一些相关类的包,比如三层中Model层都是一些数据库表的实体类.我们所用到的类都是位于各个程序集中,若需要调用某个类,就必须引用其所在的程序集. 访问级别:程序集中的类有四种访问级别,int ...

  2. 不能修改列 "。。",因为它是计算列,或者是 UNION 运算符的结果。

    修改Mapping this.Property(t => t...).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotatio ...

  3. 十三、nginx 强制下载txt等文件

    当前的浏览器能够识别文件格式,如果浏览器本身能够解析就会默认打开,如果不能解析就会下载该文件. 那么使用nginx做资源服务器的时候,如何强制下载文件呢? location /back/upload/ ...

  4. Java 基础(4)——常量 & 注释

    hello 呀,今天的内容超简单( ̄︶ ̄)↗并且,还有暗藏福利哟~~ 常量 常量 就是常常不变的量,第一次定义之后,就不会发生改变了.可能这就是 “常量” 的来源吧哈哈哈(玩笑). 一般来说,常量的定 ...

  5. MySQL prompt命令

    修改提示符,设置后挺方便的 例如: 几个好用的参数 \d 当前数据库 \u 当前用户 \h 当前主机 更多参数可以参考mysol官方文档 参考文档:https://dev.mysql.com/doc/ ...

  6. css之子元素获取(未定义高度)父元素的高度

    你可能碰到过这样的需求,一个高度不固定的区域(内容由用户创造),当鼠标经过该区域或者其神马操作时,需要出现一个与该区域一样大的模版: 我们用一个span来处理这个mask.由于 .sample-1 和 ...

  7. HDU 3191 次短路长度和条数

    http://www.cnblogs.com/wally/archive/2013/04/16/3024490.html http://blog.csdn.net/me4546/article/det ...

  8. C Primer Plus 读后感

    <C Primer Plus>中文版 第六版 从网站上搜索到这本书适合初学者而且是自学者可以看的C语言书籍,于是上网买了一本. 真是物有所值,通过本书我很系统的学习了一遍C语言,书中代码很 ...

  9. C 堆内存管理

    在Win32 程序中每个进程都占有4GB的虚拟地址空间,这4G的地址空间内部又被分为代码段,全局变量段堆段和栈段,栈内存由函数使用,用来存储函数内部的局部变量,而堆是由程序员自己申请与释放的,系统在管 ...

  10. win10 x64 python3.6 pycharm 安装statsmodels

    在pycharm下,安装statsmodels,会出现需要vc++14.0的错误提示. 这时可以到网站 https://www.lfd.uci.edu/~gohlke/pythonlibs/#word ...