ML Versus Racket

  • 函数编程模式 (with constructs that encourage a functional style)
  • 不鼓励 mutation (但提供了支持 mutation 的 constructs),头等函数模式 (first-class functions and closures),即函数可以作为参数,返回值或与变量进行绑定
  • syntax
  • ML 特别的模式匹配 (pattern matching) 机制
  • Racket 的 struct 与独特的 struct-accessor functions
  • ML 有而 Racket 没有的 静态类型 系统 (static type system),将在下面进行详细讨论
  1. 静态类型系统

与 Racket 不同,ML 会在编译时执行类型检查,将会"拒绝" (reject) 一些程序并报告错误,不让它们进入运行阶段

这是因为 ML enforces certain restrictions (例如 list 中的所有元素类型一致),从而保证了在编译时就能排除掉某些错误 (例如让一个整数和一个字符串相加)

首先我们站在 ML 的视角看 Racket

若我们将一些 Racket 程序转成 ML,我们会发现 ML 会拒绝一些确实存在 bug 的程序

(define (f y) (+ y (car y))

这个函数无论传入什么参数都会引起错误;而 ML 的静态类型系统可以识别到类型错误并在编译时阻止函数进入运行阶段

而 Racket 只有等运行到这里才会弹出 error

然而,除了这些,ML 也会拒绝一些在 Racket 视角下看起来 没有 bug 的程序

(define xs (list 1 #t "hi"))

在 ML 中,list 中的所有元素类型应该保持一致,因此这个程序会被拒绝

接下来,我们再用 Racket 的视角来审视一下 ML

有一种看法保留上面的观点,也就是 Racket 是 ML 的 superset,因为它可以接受某些被 ML 拒绝的程序

另外一种看法更加有趣:Racket is just ML where every expression is part of one big datatype

我们用 ML 语言来定义这个 big datatype

datatype theType = Int of int
| String of string
| Pair of theType*theType
| Fun of theType->theType
| ... (one constructor per built-in type)

也就是说,Racket 中的每个表达,都被某个 constructor 隐式 (implicitly) 地包装入这个 big datatype 中

例如 42 实际被隐式包装称为 (Int 42)。这样处理使得每个表达的结果都是 theType 类型的

在程序运行时,一些 semantic 例如 + 会检查参数的 标签 (tag),如果不合法就 raise error (以 + 为例,检查两个参数是否均为数字),反之将结果隐式包装在对应的 theType 内

由于 Racket 的这种 "secret pattern-matching" 是不对程序编写者暴露的 (is not exposed to programmers),Racket 提供了类型检查函数

pair? 为例,如果在 ML 中实现的话代码如下

fun pair? v = case v of Pair _ => true | _ => false

但是,与 ML 中的 datatype 不同,在 Racket 中,我们可以通过 structtheType 中动态添加新的 constructors

What is static checking?

Week 1 中提过,程序由编写完毕到运行的几个过程

  1. 由 \(parser\) 语法分析器将代码转换为 \(AST\) 抽象语法树

    若程序无法被 \(parser\) 分析,弹出的错误一般是 "syntax error" 或 "parsing error" (语法错误)

  2. \(type-checker\) 对 \(AST\) 进行类型检查

    这一过程就是我们一般提到的静态检查 static checking!弹出的错误一般是 "type error"

  3. 程序运行,若出现错误弹出 "run-time error"

静态检查的方式视乎这种编程语言的定义方式。有些语言完全不进行静态检查,例如 Racket

而定义一个语言的静态检查系统的最常见的方法就是通过 type system

我们熟悉的 ML 是这样定义 type system 的:

  • 每个变量都有其类型 (Each variable had a type)
  • 一系列类型规则 (例:条件语句的两个分支类型一致;list 所有元素类型一致等等)

ML 的 type-checker (包括其 type-inferring system) 所做的事就是检查这一系列规则是否被遵守

以上就是 ML 的 静态检查 static checking 的实现方式 (how it does it)

但并不是静态检查这一过程的目的 (the purpose of static checking),或者说效果 (what it accomplishes)

静态检查的目的是阻止 make no sense 或者 may try to misuse a language feature 的某些程序的运行,但它并不是万能的,有很多 error 都无法被静态检查检测出来

Racket 的动态检查 (dynamic checking,或者说 run-time checking) 是通过 tagging each value 实现的 (我们上面提到的 constructor 可以说是一个 tag)

而 ML 的静态检查在程序运行之前就可以排除某些有问题的程序,这样做的代价是静态检查同样可能 reject 掉某些实际上没问题的程序

ML 与 Racket 向我们揭示了,阻止某些 bug 程序一般在 compile-time 或者是 run-time

但实际上,阻止的时机并不一定只有这两个时刻,而是一个 continuum (具体看 section7sum-What is static checking,有 5 个时机)

Correctness : Soundness, Completeness, Undecidability

我们定义一个静态检查器 (static checker) 的 correctness 正确性表现在两个方面:

  • soundness 可靠性
  • completeness 完整性

对于某个我们想避免出现的错误 \(X\)

我们说一个 static checker 是可靠的 (\(sound\)) 当其能够 reject 所有对于某些输入执行 \(X\) 的程序

而一个 static checker 是完整的 (\(complete\)) 当其 accept 所有对于任意输入都 不执行 \(X\) 的程序

打个比方,我们的程序就是待检测人群,其中对于某些输入可能执行 \(X\) 的就是感染者

static checker 的目的就是在程序运行之前就检测到感染者的存在

一个可靠的 (\(sound\)) static checker 不会出现假阴性 (false negative),即虽然通过了检测,但实际上是感染者

而一个完整的 (\(complete\)) static checker 不会出现假阳性,即虽然不是感染者,却没法通过检测

现代语言的 static checker 是可靠 (\(sound\)) 但并不完整 (\(incomplete\)) 的,但我们通常会采取各种措施在不影响其可靠性的同时提高其完整性

以我们熟悉的 ML 的 static checker 为例

若我们想避免的 \(X\) 为 "string 不能作为除法运算的运算数"

fun f1 x = 4 div "hi" (but f1 never get called)
fun f2 x = if true then 0 else 4 div "hi"
fun f3 x = if x <= abs x then 0 else 4 div "hi"

以上的三个函数均不会执行 4 div "hi" 这一语句,但是 static checker 会统统弹出 type error 拒绝程序的运行

事实上,static checker 不能同时做到

  • 它是可靠的 (sound)
  • 它是完整的 (complete)
  • 它不会进入死循环 (always terminate)

为了确保 \(X\) 不出现,程序员选择了可靠性而牺牲了完整性

为什么 static checker 不能同时做到可靠与完整的原因实际上牵扯到了更深层次的东西:图灵机的不可决定性 (undecidablilty)

和经典的停机问题类似,我们的 static checker 本质上也是一段程序

将问题改为停机问题的形式:是否存在这样一段程序 (这样一种 static checker),对于任意的程序与某个输入,能够判断其运行过程中能够 精确的 避免 \(X\) 的发生?

"精确的避免" 即同时避免假阳性与假阴性,也就是兼具可靠性与完整性的 static checker,显然,这样的程序是不存在的。

Weak typing

接下来我们再来介绍一个不同的概念,强类型 (\(Strongly\) \(typed\)) 语言弱类型 (\(Weekly\) \(typed\)) 语言

首先要强调,语言的强/弱类型与动态/静态类型是两个完全不同的概念

假设某个 type checker 对于某个 \(X\) 是不可靠 (unsound) 的,通常的方法是在语言实现 (language implementation) 中加入一些 dynamic check 以避免 \(X\) 被执行

但还有一种方法:认定 \(X\) 的出现是程序员本身的问题,语言根本不去 check (哈哈哈哈哈)

\(C++\) 和 \(C\) 是非常典型的弱类型语言。举一个我非常熟悉的例子:\(C++\) 中的数组越界问题

就算我们访问了不合法的数组下标如 a[-1],\(C++\) 也不会阻止程序的运行,最后我们会得到相当奇怪的结果 (原因是访问了其他的地址)

所谓强类型语言,就是在其类型系统 (type system) 中存在较多的限制 (restrictions) ,阻止潜在 bug 程序的运行

而弱类型语言的限制就相对较少

相对于强类型语言,弱类型语言省去了进行检查的时间成本,存储数据 (例如 dynamic check 所需要记录的 tag) 的空间成本

但是所付出的代价是,程序的 bug 更依靠程序员本人来检出而不能依靠强类型语言的各种 checks 来发现 (相信我们都遇到过出现奇怪 bug,最后发现是数组越界的抓狂时刻)

最后要提的是,语言类型的强和弱比起是一种定义,不如说是一种相对的概念

可以参考这个贴子,回答者讲的很好

另外中文网上好像对强/弱类型还有另外的定义,好像是基于变量的定义和类型转换规则的

More flexible primitives is a Related but Different Issue

现在,对于一个我们想避免的 \(X\) ,不同的语言会有以下不同的处理办法

  • static checking: static checker 在 compile-time 就将其发现并阻止程序运行,弹出 type error
  • dynamic checking: 在 run-time 发现 \(X\) 并中断程序运行,弹出 run-time error
  • weak typing: \(X\) 通过所有 checker 的检查并成功被程序执行,程序员你自己找 bug 吧

还有一些语言选择扩展某些 primitive 的语义:

例如,传统的 \(+\) 只允许两个数字进行相加,若参数中出现 string 是不合法的

而在 \(Python\) 中,"Hello" + "World" 这样的表达是合法的,返回 "HelloWorld"

这是因为 \(Python\) 扩展了 + 的语义,使其在作用与两个 string 时起到连接的作用

关于是否将 \(X\) 之类本来应当视作 bug 的语句赋予新的意义这个问题见仁见智

一方面,这样会使一些 bug 更难被发现

而一方面这样的处理方式确实给程序员带来了很多方便

Coursera Programming Languages, Part B 华盛顿大学 Week 3的更多相关文章

  1. Coursera课程 Programming Languages, Part A 总结

    Coursera CSE341: Programming Languages 感谢华盛顿大学 Dan Grossman 老师 以及 Coursera . 碎言碎语 这只是 Programming La ...

  2. Coursera课程 Programming Languages 总结

    课程 Programming Languages, Part A Programming Languages, Part B Programming Languages, Part C CSE341: ...

  3. Coursera课程 Programming Languages, Part B 总结

    Programming Languages, Part A Programming Languages, Part B Part A 笔记 碎言碎语 很多没有写过 Lisp 程序的人都会对 Lisp ...

  4. The history of programming languages.(transshipment) + Personal understanding and prediction

    To finish this week's homework that introduce the history of programming languages , I surf the inte ...

  5. Natural language style method declaration and usages in programming languages

    More descriptive way to declare and use a method in programming languages At present, in most progra ...

  6. The future of programming languages

    In this video from JAOO Aarhus 2008 Anders Hejlsberg takes a look at the future of programming langu ...

  7. Hex Dump In Many Programming Languages

    Hex Dump In Many Programming Languages See also: ArraySumInManyProgrammingLanguages, CounterInManyPr ...

  8. 在西雅图华盛顿大学 (University of Washington) 就读是怎样一番体验?

    http://www.zhihu.com/question/20811431   先说学校.优点: 如果你是个文青/装逼犯,你来对地方了.连绵不断的雨水会一下子让写诗的感觉将你充满. 美丽的校园.尤其 ...

  9. ESSENTIALS OF PROGRAMMING LANGUAGES (THIRD EDITION) :编程语言的本质 —— (一)

    # Foreword> # 序 This book brings you face-to-face with the most fundamental idea in computer prog ...

  10. Comparison of programming languages

    The following table compares general and technical information for a selection of commonly used prog ...

随机推荐

  1. P12证书转BKS证书

    安卓 识别的证书格式是bks ,而我之前生成的证书格式是p12 所以需要转换一下,至于怎么生成p12,请看我转载的的文章  Nginx https 双向认证. 1.请先下载第三方转换工具protecl ...

  2. [WSL-1-Ubuntu]使用oh-my-zsh美化你的WSL(附脚本)

    在腾讯云买的那个1c2g的服务器,想用mycat搭建一个mysql cluser,用docker部署了一主一从内存就没了一半,可一主一从没啥作用,起码也得2主2从吧?而且还有HA呢,但内存和钱包不给力 ...

  3. 19JS输出杨辉三角

    <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF ...

  4. day1 AcWing 836. 合并集合

    初始化: n个下表对应n个集合,根节点的特征是父节点就是其本身. for(int i = 1; i <= n; i++) p[i] = i; M操作:如果两个元素在同一个集合中,什么也不做,否则 ...

  5. json类型数据取出想要的部分

    因为才疏学浅,只能用很笨的方法. 以下是我拿到的数据的json型数据. {"result":{"ingredient":{"result": ...

  6. 三.database阶段回顾

    阶段回顾: 1.mysql:文件管理软件 2.三部分: 服务端 sql语句 客户端 3.客户端 mysql navicat 4.授权操作 用户操作 授权操作 5.sql语句 数据库操作 create ...

  7. 从零搭建hadoop集群之hadoop集群安装

    1.集群规划   HDSF YARN hadoop01 NanemNode(主机点),DataNode NodeManager hadoop02 DataNode, SecondaryNamenode ...

  8. got multiple values for keyword argument 'to_fields'

    django.db.models.fields.related.ForeignObject.__init__() got multiple values for keyword argument 't ...

  9. mysql redis 开启远程访问

    springboot 加载外部yml nohup java -jar warehouse-0.0.1-SNAPSHOT.jar --Dspring.config.location=/root/www/ ...

  10. jar打包exe选型

    网上一搜有篇文章写了9中不同工具方法,最后使用launch4j,其实就一点开源免费. launch4j>bsd3协议(可免费商用),像exe4j需要license. 经过摸索终于搞定一键启动并且 ...