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. Bundle传递数据,Handler更新UI

    Bundle主要用于传递数据:它保存的数据,是以key-value(键值对)的形式存在的. Bundle经常使用在Activity之间或者线程间传递数据,传递的数据可以是boolean.byte.in ...

  2. Vue学习笔记:methods、computed、watch的区别

    自:https://www.jb51.net/article/120073.htm 首先要说,methods,watch和computed都是以函数为基础的,但各自却都不同 而从作用机制和性质上看,m ...

  3. [linux] C语言Linux系统编程-捕获进程信号

    typedef void( *sighandler_t)(int); 1.用typedef给类型起一个别名. 2.为函数指针类型定义别名, 3.函数指针(指向函数的指针) sighandler_t s ...

  4. Java 泛型的使用

    一.泛型的简介1.为什么要使用泛型? 一般使用在集合上,比如现在把一个字符串类型的值放入到集合里面,这个时候,这个值放到集合之后,失去本身的类型,只能是object类型.这时,如果想要对这个值进行类型 ...

  5. H5上滑跳转页面

    方法一: jquery方法 movePage($('body')); function movePage(dom) { var startY, moveY, moveSpave; dom.on(&qu ...

  6. web 开发人员必须学习的 3 门语言

    web 开发人员必须学习的 3 门语言:html  css  js HTML 定义了网页的内容 CSS 描述了网页的布局 JavaScript 网页的行为

  7. 浏览器根对象window之Location

    1. Location Location 对象包含有关当前 URL 的信息.Location 对象是 Window 对象的一个部分,可通过 window.location 属性来访问. 1.1 Loc ...

  8. 安卓app开发-04- app运行的运行和调试

    app 运行的运行和调试 本篇介绍在 Android Studio 开发工具,运行调试设备:真机和虚拟机. 真机调试(USB 连接手机) 尽量使用真机进行调试,无论是调试效果和速度都比模拟器要好.使用 ...

  9. CentOS 7 Apache 多端口部署 Web Apps 指南

    转载自简书,原作者xuyan0,链接https://www.jianshu.com/p/b34c78bf9bf0,如有侵权,请联系删除 导语 Apache web 服务器运行着互联网上超过半数的活跃的 ...

  10. java indexOf 和 split的用法

    1.java 的 indexOf 方法 ,如果存在 则 指定的字符串的开始位置,如果不存在 则返回-1: 2.java 的 split的方法:将一个字符串分割为子字符串,然后将结果作为字符串数组返回. ...