programming-languages学习笔记–第7部分

*/-->

pre.src {background-color: #292b2e; color: #b2b2b2;}

pre.src {background-color: #292b2e; color: #b2b2b2;}

pre.src {background-color: #292b2e; color: #b2b2b2;}

pre.src {background-color: #292b2e; color: #b2b2b2;}

programming-languages学习笔记–第7部分

1 ML对比Racket

有很多共同点,也有一些不同点,最大的不同是ML的类型系统。

类型检查的优点与缺点。
ML在程序运行前通过类型检查拒绝程序并报告错误。
ML通过在编译时强制一些规则来确保没有某些错误(比如不能传递一个字符串给+)。

从Racket的观点看ML:

  • 语法上,ML是Racket的一个子集:对于产生同样结果的程序,ML会认为很多程序不合法。
    优点是ML会拒绝看上去像是错误的程序。
  • 许多程序不允许有bug,比如:
(define (g x) (+ x x)) ;类型检查确保正确。
  • 实际上,ML中不需要number?这样的判断
  • 一些程序无法实现,比如:
(define (f x) (if (> x ) #t (list  )))
(define xs (list #t "hi"))

从ML的观点看Racket:

  • 可以认为Racet有一个巨大的datatype,所有的值都是这个类型:
datatype theType = Int of int | String of string
| Pair of theType * the Type
| Fun of theType -> theType
| ...
  • 构造器是隐式的(值被标记)

    • 42就是Int 42
  • primitives隐式检查标记并提取数据,对错误的构造器抛出异常

theType内置的构造器:numbers,strings,booleans,pair,symbols,procedures,等

struct-definition创建一个新的构造器,动态添加到theType.(ML中不允许,但是异常exn与此类似)

2 静态检查

静态检查在程序parse之后运行之前进行,发现其中的错误。语法解析过程中的错误叫做语法错误,静态检查的错误叫做类型错误。

执行静态检查是语言定义的一部分。
常用的做法是通过类型系统进行静态检查。比如ML中每个语言构造的类型规则。
静态检查的目的是拒绝无意义或可能试图滥用语言功能的程序。
作为对比,racket使用动态检查(运行时检查)发现错误,为不同类型的值打上标记(tag),在函数应用时检查是否符合规则。

静态检查会拒绝一些不会产生任何错误的程序。

类型系统无法防止逻辑/算法错误。

类型系统麻烦的的部分在于确定类型检查确实达到目的。

静态检查/动态检查是整个流程中的两个点,编译时和运行时。
比如要防止3/0的错误,可以在以下几个方法入手:

  • 键入时: 编辑器中不允许
  • 编译时:代码中不允许出现
  • 链接时: 不允许执行调用
  • 运行时: 当进行除法时禁止
  • 之后处理: 代替除法,直接返回+inf.0

3 正确性:可靠性,完整性,不确定性

使用精确的术语描述类型系统的正确性,比如要防止一些事情X:

  • 可靠性:当类型系统从不允许一个程序使用某些输入时执行X操作,称它为可靠的
  • 完整性:当类型系统从不拒绝一个不管什么输入都不会执行X操作的程序,就是完整的。

可以认为可靠性防止漏报,完整性防止误报。

可靠性很重要,因为它让语言用户和语言实现者可以依赖X永远不会发生。

ML的类型检查器就是不完整的,有些正常的程序无法通过检查,比如:
if true then 3 else "hi"

不确定性是计算理论研究的核心。

4 弱类型

weak typing:程序必须通过静态检查,但是运行时没有任何限制。
c/c++,最常见的就是数组越界访问。

Racket是动态类型,运行时有类型检查,不是弱类型。

5 静态类型vs动态类型

静态类型在早期发现很多错误,可靠性保证一些类型的错误不会发生,不完整性意味着一些完美的程序被拒绝。

5.1 静态类型或动态类型哪个更方便?

动态类型的优点是可以混合和匹配不同类型的数据,而不需要声明新的类型定义或使用模式匹配。

另一方面,静态类型可以假定数据有正确的类型,不需要额外的代码进行检查:

(define (cube x)
(if (not (number? x))
(error "bad arguments")
(* x x x)))
(cube )
fun cube x = x * x * x
cube

5.2 静态类型是否限制一些有用的程序?

静态类型不能实现一些程序,比如:

(define (f g) (cons (g ) (g #t)))
(define pair_of_pairs (f (lambda (x) (cons x x))))
fun f g = (g , g true) (*无法通过类型检查*)
val pair_of_pairs = f (fn x => (x,x))

静态类型可以按照需要进行tag,使用datatype,实践中很少需要,(Racket总是包含tag):

datatype tort = Int of int
| String of string
| Cons of tort * tort
| Fun of tort -> tort
...
if e1
then Fun (fn x => case x of Int i => Int (i * i *i))
else Cons (Int , String "hi")

支持静态类型的论点是现代类型系统具有足够的表达能力,很少会妨碍你的工作。

5.3 静态类型的早期错误检测重要吗?

因为静态类型检查可以捕获已知的错误类型,所以可以使用这些知识将注意力集中到其他地方。

(define (pow x)
(lambda (y)
(if (= y ) (* x (pow x (- y ))))))
fun pow x y = (* 无法通过类型检查 *)
if y =
then
else x * pow (x, (y - ))

动态类型的支持者会说静态类型检查只能发现测试中可以捕获的bug,对于语义错误无法发现。

5.4 动态类型还是静态类型能带来更好的性能?

  • 静态类型更快,语言实现:

    • 不需要存储tags(空间,时间)
    • 不需要在运行时检查tags进行类型测试(时间)
  • 动态类型反对意见:
    • 在大多数软件中,这种低水平的性能并不重要。
    • 语言实现可以优化移除不必要的tags和测试
    • 如果在静态类型系统中要突破类型系统的限制,使用变通方法也会影响性能优势

5.5 静态还是动态类型更容易代码重用?

动态类型更容易重用库函数,如果使用cons构造不同的数据,只要使用car,cdr,cadr等就可以访问,不需要为不同的数据类型定义不同的访问器函数。

静态类型的观点:

  • 现代类型系统通过generics和subtyping等特性支持代码重用
  • 如果使用cons表示所有东西,会被表示的东西弄混,并且很难调试错误。
    • 使用单独的静态类型保持思想独立
    • 静态类型可以避免库的误用

5.6 静态类型和动态类型哪个更适合原型开发

在软件项目的早期,你会开发一个原型,与此同时,你将改变对软件做什么和用什么方法实现的看法。

动态类型更容易实现原型(prototyping),在早期,你可能不知道需要什么数据类型和函数,因此不用去定义。有一部分程序还没有实现,可以测试已经写好的部分,动态类型可以让不完整的程序运行。
静态类型不允许这样的代码,,因此过早地对数据结构作出承诺,然后编写以后会扔掉的代码用来通过类型检查器 ,是令人沮丧的原型设计。

静态类型的反对观点是,静态类型更容易原型设计,使用类型系统能更好地记录你不断变化的数据结构和代码用例的决策:
新的、不断演化的代码最有可能作出不一致的假设。

5.7 静态类型或动态类型更适合于代码演进吗?

软件工程中的很多工作都花在维护工作上,修复bug、添加新特性,以及对代码进行修改。

动态类型更容易演化改进,修改代码不会影响旧的调用者,比如接受一个int或string代替一个int:
Racket调用者不用做任何修改

(define (f x) (*  x))
(define (f x)
(if (number? x)
(* x)
(string-append x x)))

ML调用者修改后必须在参数上使用构造器,并对结果使用模式匹配。

fun f x =  * x
fun f x =
case f x of
Int i => Int ( * i)
| String s => String (s ^ s)

另一方面,静态类型检查对于捕获演进过程中引入的bug很有用,修改数据或函数的类型,类型检查器会给我们一个所有要修改的地方的"to do" list:

  • 避免引入bug
  • 类型中的规范越多,类型检查器列出的类型更改时要更改的内容就越多。
  • 反方:todo list是强制性的,这导致改进过程很痛苦:不能以部分的方式进行测试。

开发项目中的现实:

  • 在实现稳定前经常需要很多原型
  • 在1.0版之后会有很多维护/演化

静态类型对比动态类型哪个好不是个好问题。更好的问题是:我们应该静态执行什么?

合理决策:以事实为依据的理性讨论。

作者: ntestoc

Created: 2019-01-09 三 11:07

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

  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. WCF 之部署(2010版本之上)

    首先,在WIN系统下打开IIS,每种操作系统是不同的,请不要弄混. 1.控制面板---左下放的程序---启用或关闭Windows功能,如图: 2.找到Internet Information Serv ...

  2. [javaSE] GUI(jar包双击运行)

    首先应该在java文件中定义包名,package 包名 带包编译成class文件 切换到目录下,使用jar -cvf xx.jar 包名,就是把那个包放到xx.jar包里面 此时双击会报错,找不到要执 ...

  3. [javaSE] GUI(Action事件)

    对自己定义的类规范化一下,事件和图形化组件分离出来 定义一个类FrameDemo 定义成员属性Frame frame 定义成员属性Botton 定义构造方法FrameDemo() 定义初始化方法ini ...

  4. veloctiy入门

    什么是velocity? velocity是一个基于Java的模板引擎.你可以使用它来预定义模板,并且对模板进行数据渲染,从而动态生成相应的文本.它如同JSP一样经常被使用在MVC分层架构当中,充当V ...

  5. Java中多个集合的交集,并集和差集

    一.交集 java中交集使用 A.retainAll(B) ,交集的结果在集合A中. import org.junit.Test; import java.util.HashSet; import j ...

  6. 【Java学习经历系列-1】19岁的我,没遇见生命中的她,却遇见了java

    [写在前面]正直青春年少的你,遇到了你的她了吗?还是你也和我们今天的主人公一样,在最美好的年级,正在为你的初衷努力着,坚持着,奔波着..... 作者:李伟   我的黑客时代 01 大学专业是电子信息工 ...

  7. mybatis必知必会二

    关联: 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型. 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集.首先,然让我们来查看这个元素的属性.所有的你都会看到,它和普通的只由 ...

  8. Java 并发:线程中断-interrupt

    一直以为执行了interrupt方法就可以让线程结束,并抛出InterruptedException. 今天看了Java并发编程实战的第七章发现并不是这么回事,在这章的开头就提到 要使任务和线程能安全 ...

  9. CF961F k-substrings

    题意 给定一个字符串 \(S\) 求所有的 \(S[i,n-i+1]\) 的 \(border\) 长度(最长的前缀等于后缀),要求长度是奇数 \(n\le 10^6\) Sol 首先发现每次求的串都 ...

  10. 关于MyEclipse2017Ci10版本的破解和Tomcat9.0的安装搭配使用

    昨天和今天就忙这两件事情了.废话不多说直接上干货! 首先是关于Myeclipse2017的破解,关于这个破解,网上的资源和文件很多,可以自行下载,我就不贴链接了. 我要说的是破解的问题,在这里我们要注 ...