Programming Languages PartA Week2学习笔记——SML基本语法

首先简单介绍使用的SML语言,参考维基百科和百度百科:

MLMeta Language:元语言)是由爱丁堡大学的Robin Milner及他人在二十世纪七十年代晚期开发的一个函数式、指令式的通用的编程语言,它著称于使用了多态的Hindley–Milner类型推论。ML能自动的指定多数表达式的类型,不要求显式的类型标注,而且能够确保类型安全,已经正式证明了有良好类型的ML程序不会导致运行时间类型错误。

ML提供了对函数实际参数的模式匹配、垃圾回收、指令式编程、传值调用和柯里化。它被大量的用于编程语言研究之中,并且是全面规定了的和使用形式语义验证了的少数语言之一。它的类型和模式匹配使得它非常适合并且经常用于在其他形式语言上进行操作,比如在编译器构造、自动化定理证明和形式验证中。

Standard MLSML),是一个函数式、指令式、模块化的通用的编程语言,具有编译时间类型检查和类型推论。它流行于编译器作者和编程语言研究者和自动定理证明研究者之中。

Standard ML是ML的现代方言,ML是用于LCF(可计算函数逻辑)定理证明计划的编程语言。Standard ML在广泛使用的语言之中与众不同,源于它具有正式规定《The Definition of Standard ML》,给出了语言的类型规则和操作语义。

ML一般被归为非纯函数式编程语言,因为它允许副作用和指令式编程。命令式语言以语句作为基本单位,ML遵循这一规则,但同时继承了函数式语言的很多特征,所以ML属于一种具备命令式语言特点的函数型语言,或者说面向函数的命令型语言。

SML又存在有多种实现:

本博客采用Standard ML of New Jersey实现方式。


本博客主要记录Coursera上华盛顿大学的Programming Language,PartA课程的学习过程,主要是学习笔记和个人思考,不一定成体系,也不一定完全对。欢迎批评指正!

本课程使用ML这种语言来入门函数式编程是合理的,因为ML同时具有函数式和命令式特点,但以命令式为基础,写起来会比较亲切(对于以C等命令式语言入门编程的人来说,尽管函数式编程的思路和面向过程、面向对象的编程思路都不太一样)

基础语法可以参考其他博客,例如:

SML基本语法(一)

SML基本语法(二)

SML基本语法(三)

SML基本语法(五)

或书籍,例如:

Programming in Standard ML (DRAFT: VERSION 1.2OF 11.02.11.)By Robert Harper, Carnegie Mellon University, Spring Semester, 2011

Week 2

Variable Binding

type checking before evaluation

变量绑定:

1、type checking -> 对变量的数据类型进行绑定 -> static environment

2、valuation -> 对变量的数值进行计算 -> dynamic environment

变量绑定的时候可以是某一个数值,也可以是一个表达式(if then else)

val x = 1
val y = if x = 1 then true else false

Rules for Expressions

Three things to think about for expressions:

Syntax: how we write down this expression

Valid types: what types are allowed for subexpressions , and what type this expression returns

Evaluation: how we run this when it’s part of a program

老师给出的例子:

Syntax:if e1 then e2 else e3where if , then , and else are keywords and e1, e2 and e3 are subexpressions
Type-checking:first e1 must have type boole2 and e3 can have any type(t), but have same type(t)the type of the entire expression is also t
Evaluation rules:first evaluate e1 to a value(v1)if v1 is true, evaluate e2 and that result is the whole expression's resultelse, evaluate e3 to be the whole expression's result

REPL and Errors

REPL: Read-Eval-Print-Loop

REPL stands for ‘Read Evaluate Print Loop.’ The REPL can be used to write and execute code one line at a time and is an alternative to writing code to a file and then compiling or interpreting the entire file before execution

当我们使用use xxx.sml 命令执行文件时,实质上是对文件中的每行代码放到REPL进行批处理

REPL类似于Python的交互模式

易错点1:if e1 then e2 else e3 语句中,e1必须为bool,且e2与e3类型相同

易错点2:SML不能使用负号表示负数,如-5,需要使用0-5的表达式,或者**~5**的形式

易错点3:没有类似C语言的隐式自动类型转换,整数除法需要两个int使用 div (返回int),实数除法需要两个real使用/(返回real)

易错点4:SML使用=表示相等(逻辑表达式),而不是“==”

混用数据类型可能报错,例如:

易错点4:与python类似,SML在除数为0时会抛出异常 uncaught exception Div [divide by zero]

Shadowing

值得注意的一点是,SML中“=”既用于变量初始化赋值和Shadowing,也被用作逻辑运算“等于”的符号

1、**数据类型相同时****的Shadowing,与其他语言的重新赋值类似,Shadowing后的变量不会影响已有变量的值,例如:

(* variable value shadowing *)
val x = 12;
(* x -> int *)
(* x -> 12 *)val y = x+8;
(* y -> int *)
(* y -> 20 *)val x = 250; (* shadowing *)
(* x -> 250 *)val z = y;
(* z -> 20 *)

2、数据类型不同时的Shadowing,类似Python改变变量的引用对象,可以赋值不同类型,例如:

(* variable type shadowing *)
val i = 1;
(* i -> int *)
(* i -> 1 *)val i = "123";
(* i -> string *)
(* i -> "123" *)

Functions

与大多数语言类似,函数需要先声明才能调用,

(1)函数声明或函数绑定(Function binding),SML的函数以“=”连接函数名和函数内容(表达式),没有return关键字,例如:

(* syntax *)
fun functionName(arg0 : type0, arg1 : type1, ...) = expression
(* examples *)
fun pow(x:int,y:int) = if y=0 then 1 else x*pow(x,y-1)
fun cube(x:int) = pow(x,3)

值得注意的一点是,与Java等语言不同,SML的函数声明同时,函数本身就作为一个value被添加到dynamic environment当中,同时,函数将具有 (t1 * … * tn) -> t 的类型(type-checking),其中的“*”表示连接两种类型,例如下面运行结果:

函数如果没有参数,他的类型就是unit -> t

(2)函数调用(Function call),例如:

val sixtyfour = cube(4)
(* 函数调用时的括号不是必须的,如果只有一个参数,上式也可用空格分割 *)
val sixtyfour = cube 4

值得注意的是,e1 … en都可以是一个表达式,在evaluate时计算其值;e0是一个函数,在evaluate时会被关联到相应的函数上进行计算

Pairs and Other Tuples

1、Pairs(2-tuples)

相当于一个二元tuple,其中能容纳两个元素(e1,e2)

假设e1 -> type1 ,e2 -> type2 ,则

(e1,e2) 具有 (type1 * type2),有时也可以不加括号

Pairs的建立和取出元素值,例如:

(* 函数表达式的括号内建立了一个新的pair *)
(* int*int -> int*int *)
fun div_mod(x:int,y:int) = (x div y , x mod y)(* 取出pair的元素值 *)
(* (int*int)*(int*int) -> int *)
fun sum_two_pairs(pr1:int*int, pr2:int* int) = (#1 pr1) + (#2 pr1) + (#1 pr2) + (#2 pr2)(* pair元素可以不同类型 *)
(* int*bool -> bool*int *)
fun swap(pr:int*bool) = (#2 pr,#1 pr)

2、Tuples

更一般的,多个元素用小括号括起来就是tuple,binding规则和元素获取方法与pairs类似

Lists

列表绑定:

type chcking:

​ 假设v1,…,vn都具有type t

​ [v1,v2,…,vn] -> t list

valuate:

​ 计算每个v表达式的值a

​ 得到 [a1,a2,…,an]

(* 空列表类型为 'a list,无法确定具体类型时就是'a *)
w = []
(* w -> 'a list *)x = [1,2,3]
(* x -> t list *)
(* x -> [1,2,3] *)y = [[1,2],[2,3],[5]]
(* y -> t list list *)
(* y -> [[1,2],[2,3],[5]] *)z = [(1,2),(2,3)]
(* z -> (int*int) list *)

值得注意的是:e1::e2 ,e1是一个元素, e2是一个列表,不能用于列表连接列表,列表连接列表需要递归实现或者使用 e1 @ e2

类似的连接两个字符串使用 ^ 符号

获取列表数据:

值得注意的是:null是一个函数,其类型是 'a list -> bool,用来检测列表是否为[]

hd 取出列表的第一个元素,其类型是 ‘a list -> 'a

tl 取出列表除第一个元素外的子列表,其类型是一个 'a list -> 'a list

需要注意的是,list前的type如果是元组之类的,需要用括号括起来,例如 (int*int*int) list,表示三元元组的列表

List Functions

由于Lists的取值需要用到hd(取第一个元素)和tl(取除第一个元素外的子列表),想要遍历列表需要不停的调用这两个方法,因此递归(Recursion)在List调用中是一种非常重要的方式。

例如:

fun sum_list(xs : int list) = if null xsthen 0 else hd xs + sum_list(tl xs)(* (int list) * (int list) -> int list *)
fun append(xs:int list, ys:int list) = if null xsthen yselse (hd xs)::(append((tl xs),ys))fun sum_pair_list(xs:(int*int) list) = if null xsthen 0else #1(hd xs) + #2(hd xs) + sum_pair_list(tl xs)fun firsts(xs: (int*int) list) = if null xsthen []else (#1 (hd xs))::firsts(tl xs)fun seconds(xs: (int*int) list) = if null xsthen []else (#2 (hd xs))::seconds(tl xs)fun sum_pair_list2(xs: (int*int) list) =(sum_list (firsts xs)) + (sum_list (seconds xs))

基本语法回顾1

Let Expressions

作用域Scope:在绑定之后的部分和let中的表达式部分可以调用

Let 关键字用于Local Binding,类似于局部变量声明。用在函数内部可以提升函数的灵活性(否则函数就只能通过if then else的基本语句来赋值)。如果把函数中的表达式的值类比为其他语言的return的值,那么Let让SML的函数也能像其他语言的函数一样有一个局部作用域(Local Scope),定义局部变量以在函数表达式中使用。

Let中表达式部分是body部分,其值为整个Let语句的值。

fun silly1(z:int) = let val x = if z>0 then z else34val y = x+z+9inif x>y then x*2 else y*yend(* 表达式所属的最近let定义了其局部作用域 *)
fun silly2() = letval x = 1in (let val x = 2 in x+1 end) + (let val y = x+2 in y+1 end)end

Nested Functions

嵌套函数,类似JavaScript的嵌套函数,子函数在函数内部定义,只能在函数内部调用。函数内部定义了一些内部binding,则子函数可以看做闭包,子函数可以使用其位于的作用域内的其他binding。

例如:

fun countup_from1(x:int) = let fun count(from:int, to:int) = if from=tothen to::[]else from::count(from+1,to)in count(1,x)end(* Better version *)
fun countup_from1(x:int) = let fun count(from:int) = if from=xthen x::[]else from::count(from+1)in count(1,x)end

函数设计中可重用性的代价:代码重用可以节约时间并减少bug,但可重用的代码更难适应改变。

Let and Efficiency

由于SML遍历列表的方法必须用到递归,但多次调用递归会导致算法的时间复杂度大幅度增长,例如下面的例子在函数中用了两次递归:

Buying a faster computer won’t help much!!!

使用Let保存递归的返回值,能够防止反复调用相同的递归,例如:

Options

由于SML中处理空列表或一个元素的列表是很常见的,之前往往通过随意赋值,例如空列表的情况为表达式赋值0或1或者[]来防止异常产生。为了更严谨和简化处理这一问题,使用Options。

在实际使用中需要注意 t option是一种类型,函数如果在处理空列表时返回NONE,那么在非空时也应该返回一个 t option(例如下面例子中的SOME (hd xs) ),同时递归时的返回值当然也是t option,所以tl_ans也是一个t option(取值的时候先isSome判断再valOf取出其中的值)。由于函数返回t option,在调用函数时也需要使用valOf获取返回option的值。

为了尽可能减少isSome方法的调用,第二种max实现方式是使用嵌套函数,这种方式实质上与上一节的good_max函数实现类似,只是有两点不同:

(1)将空列表判断返回NONE,最大值返回SOME,整个函数返回值是int option

(2)将递归求非空列表最大值的部分封装成了一个子函数,子函数返回值是int

Booleans and Comparison

逻辑运算符

andalso,orelse,not。其中andalso和orelse只是两个连接词,但not是一个函数

值得注意的是andalso和orelse具有与其他语言的and(&&)和or(||)类似的短路特性,即例如

val z = 3;
(* 此时由于z不等于2,所以(4+5)<10 不会被valuated *)
val x = if (z = 2) andalso (4 + 5 < 10)then 5else 0

但这三个逻辑运算符不是必要的,因为均可以表示为SML中的基本表达式if then else的形式

比较运算符

比较运算符

> < >= <=不能比较两个不同类型的值的大小,例如一个real和一个int不能比较,需要显式地类型转换:

(* 可以比较 *)
3 > 2
3.0 > 2.0(* 不能比较 *)
3.0 > 2(* 显式类型转换 *)
3.0 > Real.fromInt 2

同时,与其他语言类似,相等和不相等可以用于任意两个可比较的内容比较是否相等,但不能用于real,因为real相当于其他语言的float,计算机没办法确定两个浮点数是否完全相等

Benefits of No Mutation

SML中的变量值不能被更改(Shadowing只是改变了变量的引用),列表元组的元素也当然不能修改,下面是老师给出的说明不可更改的好处的例子:

上图的第一个函数返回的pr是pr本身的别名(相当于C++里的引用的含义),而第二个函数返回的是另一个元组。这两个函数从外部看不出任何区别,但却会导致如下的问题:

y是否是x的别名取决于函数内部的实现,这会导致后续修改时的不同,例如修改x可能导致y变化。当然,例如C++这类的语言如果在函数参数中仅仅传递数值是不存在这个问题的,但一旦传递指针或引用就需要格外小心返回的变量是否是原变量的别名。

所以SML中规定这些值不能够被修改,好处就在于我们不需要关注函数的内部实现是否会导致两个变量名指向同一个地址的问题,因为无法修改,所以其影响非常小。我们不需要考虑y是x的拷贝或是别名,相当于是屏蔽了变量的底层细节。下面的例子说明了同样的道理:

以Java的引用作为反例,如下,可以看到如果获取到的变量是作为私有变量的一个别名(指向同一个地址),则可以从外部获取修改私有变量的值,这样带来了安全性问题:

Pieces of a Language

“编程语言的哲学”

学习一门语言的5项关注点:Syntax语法或句法、Semantics语义、Idioms习惯用法、Libraries库、Tools工具

Syntax是语言的基础,但学习重点在于关注Semantics和Idioms(以及其中表现出的编程思想),Libraries和Tools在实际使用这门语言时再重点关注

Programming Languages PartA Week2学习笔记——SML基本语法相关推荐

  1. Programming Languages PartA Week3学习笔记——SML基本语法第二部分

    文章目录 Building Compound Types Records Tuples as Syntactic Sugar Datatype Bindings Case Expressions Us ...

  2. Programming Languages PartA Week5学习笔记——SML进阶与编程哲学

    文章目录 Week5 Introduction What is Type Inference ML Type Inference Type Inference Examples Polymorphic ...

  3. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 原文:Introduction to 3 ...

  4. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 原文:Introduction to 3D Game P ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 原文: Int ...

  6. python基本语法语句-python学习笔记:基本语法

    原标题:python学习笔记:基本语法 缩进:必须使用4个空格来表示每级缩进,支持Tab字符 if语句,经常与else, elif(相当于else if) 配合使用. for语句,迭代器,依次处理迭代 ...

  7. python3语法都相同吗_python3.4学习笔记(一) 基本语法 python3不向下兼容,有些语法跟python2.x不一样...

    python3.4学习笔记(一) 基本语法 python3不向下兼容,有些语法跟python2.x不一样,IDLE shell编辑器,快捷键:ALT+p,上一个历史输入内容,ALT+n 下一个历史输入 ...

  8. java基本语法心得_Java学习笔记(一)——基础语法(上)

    Java学习笔记(一)--基础语法(上) 软件构造 写在前面 编写Java程序时,应注意以下几点:大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的. 类名:对于所有的 ...

  9. 《PHP学习笔记——PHP基本语法》

    <PHP学习笔记--PHP基本语法> 前言: PHP是一门服务端脚本语言,像JavaScript一样,也是一门弱类型语言.弱类型语言最大的特点是允许变量隐式转换.这样,相对于Java这种强 ...

最新文章

  1. DataGrid和GridView单击背景变色双击颜色还原
  2. 吴恩达《Machine Learning》精炼笔记 12:大规模机器学习和图片文字识别 OCR
  3. vue手机端回退_vue移动端弹框组件,vue-layer-mobile
  4. 电机控制pid_微电机控制如此简单,揭秘微电机调速的控制,PID控制之双环调速...
  5. b+树时间复杂度_数据结构:线性表,栈,队列,数组,字符串,树和二叉树,哈希表...
  6. 如何选择白盒测试工具
  7. cs6给画笔分组_画笔工具,PS cs6笔刷入门介绍
  8. 如何在IDM官网安装IDM谷歌插件?
  9. 利用matlab命令窗口绘制simulink仿真示波器波形的方法,利用MATLAB命令窗口绘制Simulink仿真示波器波形的方法...
  10. 传统架构 vs 云原生架构,谈谈为什么我们需要云原生架构?
  11. 迪文屏DMT12800K070_A2WTC踩坑实录(二)
  12. 几个冷门linux与BSD发行版中文学习论坛
  13. java编写记事本_如何用JAVA程序编写一个记事本
  14. Windows系统下通过文件路径进入相应DOS界面
  15. Quartus ii 中ROM ip核的应用
  16. 统一自定义idea和eclipse代码格式化和注释格式化总结
  17. 计算机名和DNS域名的关系,域名、DNS、IP地址的对应关系
  18. 水瓶座06年3月运程
  19. 一键爬取王者荣耀全皮肤高清图片【方法二】
  20. Spin和Promela复习

热门文章

  1. Apache URL重定向指南
  2. Oracle Demo库默认用户/密码为什么叫Scott/Tigger
  3. 【Android】手机远程登录Windows服务器(使用Rd Client)
  4. 【Day3.4】东台吉尔湖
  5. 类似腾讯手机管家应用源码完整版
  6. 小米最新发布的“一指连”UWB技术了解下
  7. 如何迅速成为Java高手-王维树
  8. 【深度学习】5:CNN卷积神经网络原理
  9. 马海峰,杨家海,计算机应用,杨家海
  10. 轻松制作短视频网页工具:锐视RyView 帮助文档