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

2023-05-16

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

image-20220421172237025

type checking before evaluation

2image-20220421173119561

变量绑定:

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

image-20220422101154255

老师给出的例子:

Syntax:
	if e1 then e2 else e3
	where if , then , and else are keywords and 
	e1, e2 and e3 are subexpressions
Type-checking:
	first e1 must have type bool
	e2 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 result
	else, 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的交互模式

image-20220423095955026

易错点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使用=表示相等(逻辑表达式),而不是“==”

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

image-20220424113208318

易错点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 *)

image-20220424111159832

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

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

val i = "123";
(* i -> string *)
(* i -> "123" *)

image-20220424113832494

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)

image-20220504214210553

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

image-20220504213016365

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

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

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

image-20220504215315705

image-20220504215456243

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

Pairs and Other Tuples

1、Pairs(2-tuples)

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

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

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

image-20220504220405559

image-20220504220631397

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类似

image-20220504221721447

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 *)

image-20220506194810571

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

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

获取列表数据:

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

image-20220506202703858

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 xs
	then 0 
	else hd xs + sum_list(tl xs)

(* (int list) * (int list) -> int list *)
fun append(xs:int list, ys:int list) = 
	if null xs
	then ys
	else (hd xs)::(append((tl xs),ys))

fun sum_pair_list(xs:(int*int) list) = 
	if null xs
	then 0
	else #1(hd xs) + #2(hd xs) + sum_pair_list(tl xs)

fun firsts(xs: (int*int) list) = 
	if null xs
	then []
	else (#1 (hd xs))::firsts(tl xs)

fun seconds(xs: (int*int) list) = 
	if null xs
	then []
	else (#2 (hd xs))::seconds(tl xs)

fun sum_pair_list2(xs: (int*int) list) =
	(sum_list (firsts xs)) + (sum_list (seconds xs))

image-20220508152843645

基本语法回顾1

image-20220509170202640

image-20220508153413627

Let Expressions

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

image-20220509170253089

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

image-20220508162105467

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

fun silly1(z:int) = 
	let 
		val x = if z>0 then z else34
		val y = x+z+9
	in
		if x>y then x*2 else y*y
	end

(* 表达式所属的最近let定义了其局部作用域 *)
fun silly2() = 
	let
		val x = 1
	in 
		(let val x = 2 in x+1 end) + (let val y = x+2 in y+1 end)
	end

image-20220508195750672

Nested Functions

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

image-20220508212100335

例如:

fun countup_from1(x:int) = 
	let 
		fun count(from:int, to:int) = 
			if from=to
			then 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=x
			then x::[]
			else from::count(from+1)
	in 
		count(1,x)
	end

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

image-20220508213003381

Let and Efficiency

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

image-20220508214908433

image-20220508215710146

image-20220508220527142

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

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

image-20220508220130901

Options

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

image-20220509104155859

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

image-20220509110139133

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

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

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

image-20220509111131948

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 5
        else 0

image-20220509152521021

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

image-20220509154054492

比较运算符

比较运算符

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

(* 可以比较 *)
3 > 2
3.0 > 2.0

(* 不能比较 *)
3.0 > 2

(* 显式类型转换 *)
3.0 > Real.fromInt 2 

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

image-20220509154351626

Benefits of No Mutation

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

image-20220509191620923

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

image-20220509191703083

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

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

image-20220509194010818

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

image-20220509195014068

image-20220509195024451

Pieces of a Language

“编程语言的哲学”

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

image-20220509195409182

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

image-20220509195829199

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Programming Languages PartA Week2学习笔记——SML基本语法 的相关文章

  • Docker学习笔记之常见 Dockerfile 使用技巧

    0x00 概述 在掌握 Dockerfile 的基本使用方法后 xff0c 我们再来了解一些在开发中使用 Dockerfile 的技巧 这一小节的展现方式与之前的略有不同 xff0c 其主要来自阅读收集和我自身在使用中的最佳实践 也许这里面
  • Docker学习笔记之通过 Dockerfile 创建镜像

    0x00 概述 由于 Docker 镜像的结构优势 xff0c 使它的占用空间远小于普通的虚拟机镜像 xff0c 而这就大幅减少了 Docker 镜像在网络或者其他介质中转移所花费的时间 xff0c 进而提高了我们进行迁移部署的效率 不过
  • K8S学习笔记之ETCD启动失败注意事项

    最近搭建K8S集群遇到ETCD的报错 xff0c 报错信息如下 xff0c 一定要关闭防火墙 iptables和SELINUX xff0c 三个都要关闭 xff01 xff01 Mar 26 20 39 24 k8s m1 etcd 643
  • 硬件笔记之Thinkpad T470P更换2K屏幕

    0x00 前言 手上的Thinkpad T470P屏幕是1920x1080的屏幕 xff0c 色域范围NTSC 45 xff0c 作为一块办公用屏是正常配置 xff0c 但是考虑到色彩显示和色域范围 xff0c 计划升级到2K屏幕 2k屏幕
  • Kafka学习笔记之Kafka High Availability(下)

    0x00 摘要 本文在上篇文章基础上 xff0c 更加深入讲解了Kafka的HA机制 xff0c 主要阐述了HA相关各种场景 xff0c 如Broker failover xff0c Controller failover xff0c To
  • Boyer-Moore算法的C++实现

    BM算法 阮一峰的网络日志 以上给出了通俗易懂的算法讲解 xff0c 下面给出代码实现 xff0c 使用的宽字符 xff0c 这样就不限于英文字母了 stdafx h编译不过去就屏蔽掉 StringSearch BoyerMoore cpp
  • Kafka学习笔记之Kafka Consumer设计解析

    0x00 摘要 本文主要介绍了Kafka High Level Consumer xff0c Consumer Group xff0c Consumer Rebalance xff0c Low Level Consumer实现的语义 xff
  • Kafka学习笔记之Kafka性能测试方法及Benchmark报告

    0x00 概述 本文主要介绍了如何利用Kafka自带的性能测试脚本及Kafka Manager测试Kafka的性能 xff0c 以及如何使用Kafka Manager监控Kafka的工作状态 xff0c 最后给出了Kafka的性能测试报告
  • 用Clion运行C++代码时输出中文乱码解决方法

    用Clion运行C 43 43 代码时输出中文乱码解决方法 1 File gt setting 2 在页面最下面找到UTF 8 xff0c 将其改成GBK 3 根据提示选择Convert 4 问题解决啦
  • Java核心技术卷1:基础知识(原书第10版)

    本书为专业程序员解决实际问题而写 xff0c Java基础知识面覆盖很完整 xff0c 可以帮助你深入了解Java语言和库 在卷I中 xff0c Horstmann主要强调基本语言概念和现代用户界面编程基础 xff0c 深入介绍了从Java
  • 斜率K的意义

    夹角公式 设直线l1 l2的斜率存在 xff0c 分别为k1 k2 xff0c l1到l2的转向角为 则tan 61 k2 k1 xff09 1 43 k1k2 xff09 l1与l2的夹角为 xff0c 则tan 61 k2 k1 xff
  • 阿里巴巴 2014校招 研发工程师 笔试

    刚杭州这边阿里巴巴校招笔试回来 回忆一下题 xff0c 为大家将来的笔试做点准备 选择题 xff1a 1 字符串 alibaba 的huffman编码有几位 2 以下哪些用到贪婪算法 xff1a 最小生成树的Prim算法最小生成树的Krus
  • Bag of Words(词袋模型)

    词袋模型的提出是为了解决文档分类 xff0c 主要应用在 NLP Natural Language Process xff0c IR Information Retrival xff0c CV xff08 Computer Vision x
  • 全向和定向天线区别,何为天线增益

    文 白浪 为什么网络信号弱 速率低 时断时续 xff1f 为什么购买了大量的AP xff0c 但是还有地方信号不好 xff0c 而有的地方信号多到互相干扰 xff1f 为什么布置了大增益的天线 xff0c 结果还未能得偿所愿 xff1f 无
  • 2017电赛国赛可见光定位装置(精确度达到1cm)

    先不多说上源码 Github网址 xff1a https github com DerrickRose25 Indoor positioning of visible light 用的畸变摄像头 xff0c 用Matlab做线性拟合和畸变矫
  • 如何给图片添加黑色边框

    有时候图片有了黑色边框才能与相近的背景区分开 xff0c 这个时候给图片添加边框就是必须的了 你好这位朋友 xff01 很简单 xff0c 要这样操作 xff1a 将照片打开 xff0c 裁切好后 xff0c 应用工具箱中的矩形选择工具 x
  • STLINK怎么与STM32单片机连接

    STLink是ST官方开发的单片机仿真工具 xff0c 可以烧写程序 在线仿真 xff0c 使用非常方便 STLink具有两种接口 xff0c 分别为 1 SWD模式 2 SWIM单总线模式 SWD模式主要针对STM32系列的单片机 xff
  • 【树莓派4B为例的树莓派接口认识】

    1 xff1a SOC芯片 树莓派采用博通 xff08 Broadcom xff09 BCM2711芯片作为SOC芯片 xff0c 芯片上集成了CPU GPU DSP及SDRAM内存等 xff0c 其中CPU和GPU共享内存 xff0c 可
  • 仿真阿克曼小车+3D激光雷达velodyne并使用lego-LOAM与octomap建图

    仿真的学习内容基于以下项目 xff0c 然后将2D激光雷达换成3D的velodyne试了试效果 xff0c 想跑lego loam然后用octomap转成2D栅格地图看能不能导航 阿克曼小车仿真开源 首先需要下载当前ros版本的velody
  • 树莓派3B安装Ubuntu 18.04

    这里展示的是使用显示器的方法 xff0c 不用ssh 树莓派3b安装Ubuntu18 04完全遵照的Ubuntu wiki中的步骤 如果产生显示器显示问题可以看树莓派与电视之间的显示问题 xff08 1 xff09 下载并写入 下载Ubun

随机推荐