Programming Languages PartB Week3学习笔记——动态还是静态?编程语言哲学

2023-05-16

文章目录

      • ML Versus Racket
      • What is Static Checking?
      • Soundness and Completeness
      • Weak Typing
      • Static Versus Dynamic Typing, Part One And Part Two
      • Optional: eval and quote

ML Versus Racket

本章主要比较静态类型与动态类型语言的差别,基本上就是编程语言哲学了,挺抽象的,学起来还是需要一些自主思考。先比较ML和Racket。最大的区别是有无type system(不过Racket有Typed Racket,但不在我们课程范围内)

image-20220604110708868

image-20220604114751241

从双方的视角来看ML或Racket

抛开语法之类的东西不谈,ML是一个定义的很好的Racket子集,ML能运行Racket的部分程序,也会拒绝Racket的部分程序(例如类型检测不通过的)。这一部分被拒绝的程序,有可能是存在bug的程序(理应被拒绝),但也有可能是因为限制太严格被拒绝(不会有bug的程序,不应该被拒绝)。

image-20220604115201346

然后从ML的视角来看Racket,认为Racket是一个庞大的数据类型,相当于把Racket各种类型看做某种datatype的值,这样所有的Racket类型都是同一种datatype(在ML中就被判断为同一种类型),因此理所应当地能被混合应用(比如list中既有Int也有String,他们都被看做theType的值)

image-20220604121303037

image-20220604122256577

What is Static Checking?

首先定义static checking。type system只是static checking的一种实现方式。

type system的作用包括:

(1)为每个变量、表达式等赋一个type

(2)目的是防止基元误用、加强抽象、避免动态类型检查(有了静态类型检查就不需要运行时检查类型了)

image-20220604122430881

以ML为例:type system需要保证程序运行时不会出现一下情况(拒绝一些情况):

image-20220604123128980

必须注意的是,静态检测static checking是语言定义的一部分,对于某种语言而言,它的检查规则就是一定的。如果修改其中某些规则(例如删除pattern match 中的冗余pattern检测),虽然不会导致什么特别大的bug,但整个type-checking发生了一些细微的改变,所以语言定义本身也改变了。

相当于我们实现了一种稍微不同的语言,因为静态检查接受了一组不同的程序。按老师的话来说:

静态检查是语言定义的一部分,因为它影响合法程序的内容。ML中的冗余模式检查有点不寻常,因为如果我们删除它,并没有什么“不好”的事情发生——我们只是有一些程序不能使用某些分支。尽管如此,如果我们改变了语言实现执行的静态检查,那么我们就改变了语言。

type system也要允许程序出现某些情况,让某些情况在运行时判断(某些与类型本身无关且无法在未运行前就发现的问题):

image-20220604124113915

拒绝一些情况的目的:

image-20220604124447683

我们既希望在一个bug造成严重问题(变得严重)之前捕获它,也希望在bug不严重时不被过分汇报。这两者之间存在一种固有矛盾。

动态和静态检查是在一个连续过程中的两个关键点,他们有不同的急迫(eagerness)程度。

image-20220604124712776

Soundness and Completeness

type system的稳健性和完整性,二者通常是相悖的。

稳健是不会通过任何出现X的程序,没有漏报(假阴性,false negatives)发生

完整是不会拒绝任何没有出现X的程序,没有误报(假阳性,false positives)发生

通常,一个编程语言的type system的目标是稳健,而不是完整。我们让type system 更“花俏”(fancy)是为了在绝对稳健的情况下追求更少的误报(保证用户能够使用其他不被误报的方式来通过type check)

image-20220604130002078

ML静态检查是典型的Sound but not Complete。ML一些误报的例子:

image-20220604131810212

不完整的原因:

很多因素在静态环境中是无法决策的,无法在静态时判断(所以不得不一刀切)。

没有一个static checker能够同时(1)总是终止(不运行,静止)(2)稳健(3)完整

如果有一个检查器,当且仅当没有错误参数数目错误发生时,它会接受一个程序,这将是有用的。但是,没有一个总是终止的过程可以为编程语言实现这样的检查器(除非该语言限制太大,不能实现所有程序)。

image-20220604132155129

那么编程语言如何处理不稳健的情况呢(因为编程语言通常都需要实现完全稳健的目标)。

如果选择直接允许X通过(比如为X情况设置一个默认值),那么更糟糕的情况可能发生,可能我们允许了X,同时让程序变得可能出现任意情况的错误(之后使用C/C++讨论这一点)。

image-20220604134658964

Weak Typing

本节前言:强类型和弱类型的概念一直是比较很模糊的:

(1)有些人认为有严格的类型限制,不能进行类型之间的转换(不论是显式还是隐式)才能算的上强类型;

(2)而有些人用“强类型语言”称呼那些,省略了隐式类型转换(就是说编译器为了利益编程者而插入的转换)的语言(也就是只能显式转换的语言),对于他们而言,编程语言是强类型的,如果类型必须通过通常叫做“强制”的显式符号来转换;此时的C/C++、Java等都是弱类型的。

(3)还有些人认为**(可能有误解在其中),必须先声明再使用,且类型必须严格符合定义声明、不可改变的语言就叫强类型(强类型定义语言),这种定义里面C/C++、java、C#都算是强类型语言(如果想要将某种类型和其他类型同时应用,就要(显示或隐式)“强制”**类型转换),而JavaScript这种属于弱类型(能将某种类型不进行任何转换,就视为其他类型进行应用)。

持第三类观点的人是居多的,如果在谷歌百度上搜索,也通常会出现第三类定义。但这并不代表谁人多谁就是对的,很多人对编程语言的基本认知是有误的(有些人连强弱类型和动静态类型都分不清楚,写些博客和文章来误导人)。

强弱类型其实是类型检查的严格程度,严格程度没有一个绝对的标准,很多时候是相对的,比如C可能比JavaScript更严格,然后有一种静态的语言(不运行运行时任何修改,包括类型转换),那么他就比C更严格,更“强类型”。

但如果实在要给一个客观的标准线来区分强弱类型,那么上述三种标准都有一定的道理。

甚至,在课程中,老师给出了weak typing的另一种概念,在这种标准下,C/C++是(相对)弱类型,而Java、Python等是强类型。

很多时候,明晰一个概念是对学习编程有用的(脑子里有一定了解),但这不代表我们在真正应用时需要死死地去纠结这些“无用的术语”,别被概念框死了,WIKI上有一句话:

编程语言专家 Benjamin C. Pierce,《Types and Programming Languages》和《Advanced Types and Programming Languages》的作者,曾说:

“我花了几个星期…试着弄清楚“強类型”、“静态类型”、“安全”等术语,但我发现这异常的困难…这些术语的用法不尽相同,所以也就近乎无用。”

回到本节的课程:

weak typing在这里是指某些语言例如C/C++,使用了static checking,但dynamic checking是可选的(并且在实际中不会动态检查),所以也不能检查出某些动态的错误(静态检查难以检查运行时错误)。

这样虽然让程序实现变得更简单(将检查工作留给了编程者),让程序表现效果更好(时间上不需要动态检测,空间上不需要储存动态检测的变量、环境等),让程序更“低级”(更接近底层,编译器中没有数组size等信息,所以不会做这些检测)。

老师最后纠正了一下weak typing弱类型的定义:指那些既不静态类型检测也不动态类型检测的语言。

image-20220604144805463

image-20220604151520458

所以像C/C++这种“弱类型”属于是**“strong type for weak minds"**,在静态检查上很严格、但不做或少做动态检查(把这部分的工作交给人类程序员)。大型程序是不能容许漏检的,一个bug就会让程序十分脆弱。因此作为程序员,需要计算机能给的所有帮助,而不是weak minds。

**Racket是个动态类型的解释型语言,但它不是弱类型的语言。**虽然它(大部分)只进行动态检查,但它在动态检查中检查了尽可能多的事情(这也说明强弱与动静态检查本身实际上关联不大,而是取决于是否做了足够的检查)。动态检查的定义是,如果实现可以分析代码以确保某些检查是不需要的,那么它可以优化掉它们。

当然Racket也会有一些静态检查(例如macro和未定义变量)

image-20220604152609067

再次解释课程中的weak typing弱类型

弱类型实际上是指一种类型系统,该类型系统允许正确的语言实现对程序做任何事情,甚至是与程序中的代码完全无关的事情

另外一些错误观念:

哪些行为是基元定义的,哪些是会导致错误的

这不是关于动静态类型的讨论,而是关于“什么是基元定义的运行时语义”,是一个语义问题(之前讲过,语义关乎evaluation)。我们对这些语句的不同对待结果,来自于我们不同的evaluation定义,比如某些语言定义时“foo” + “bar”就会被evaluate成为字符串相连接,但某些语言没有定义这样的evaluation规则,自然就在check时不允许。(因此这当然就不是动静态类型的问题,而是语义定义(也就是evaluation rules)的问题)

image-20220604153921055

Static Versus Dynamic Typing, Part One And Part Two

前面比较了各种语言,也讲了很多预备知识,现在正式来对比静态和动态类型

image-20220604172156461

(1a)dynamic更便利的场合,例如创建更多元化的列表或者直接返回one-of的类型

image-20220604172317416

(1b)static更便利的场合:可以假设数据拥有期待的数据类型,不会有动态检查的语言错误或者逻辑错误的errors等(因为静态检测帮编程者检查过type了,例如ML中的x*x*x(根据*乘法运算规则)就一定被判断为数值类型比如int*int*int)

image-20220604174739437

(2a)static拒绝有用的程序:这点很好理解,因为static完整性更差,所以会拒绝更多程序,以求严格的type checking不漏检。下面的例子就是因为ML function的参数type check必须是同一类型,所以被ML拒绝,而Racket不存在这个问题。

image-20220604175353146

(2b)static让编程者能够在需要的时候标记:相比于动态语言对每个类型的值都标记或记录到环境(耗费大量时间、空间,并且有error风险),static可以通过自定义datatype或者类似结构,给需要tag的内容tag。要理解这一点,就需要回顾本章第一节,**“从ML的视角来看Racket“**的内容,动态语言相当于为每个类型都进行tag。

image-20220604175916027

(3a)static更早捕获bug:这一点不难理解,比如某些类型不匹配的问题,static在编译时就发现问题,但dynamic需要在运行时才有可能发现。下面的例子是函数调用方法不对,也是一种类型不匹配问题,Currying的函数必须用Currying的方法调用(否则参数类型肯定不匹配,比如ML的pow x y,应该是两个int参数,但pow(x,y-1)就只有一个元组参数,这是不匹配的类型。但Racket中,只有运行时,函数真正被调用时,才会发现这个类型不匹配问题)

image-20220604181240274

(3b)static只能捕获一些简单的bug(其实算是能够更早捕获bug的代价):某些错误只有运行时才能捕获(甚至有些逻辑错误运行时都不能捕获),这些错误当然就不能被static捕获到。

image-20220604182055178

(4a)static更快:程序实现时在时间空间上都有一定优势(运行时不存储tag,不检查tag),编写代码时也不需要检查参数和结果(的类型)

image-20220604192724833

(4b)dynamic也有更快的场合:程序实现时也能够实现优化,去去掉一些不需要的tag和test(以加快速度减少空间),即使这样做实现比较难。编写代码时不需要手动去编写一些额外的tag来添加type-system的限制。

例如下面例子中,两个y只需要检测其中一个(tag和test),并且因为x的值来自于两个y相加的运算,x的类型也不用检测。

image-20220604194013958

(5a)dynamic的代码重用更简单:这不难理解,因为编译时不检查类型,动态语言实现多态更容易。

image-20220604200624661

(5b)static代码重用更简单的场合在于:现代语言提供可重用的特性,例如泛型和子类型等。不过这些特性的使用也会带来混淆,让代码更难以debug。

image-20220604200807227

小结,5个角度的比较。

现实中,程序都是在不断迭代的,从原型的实现,到正式版的维护升级。所以比较动静态语言也需要从这个角度来看。

image-20220604201238304

(6a)dynamic更适合实现原型的方面在于:可能不确定你需要的数据类型或者函数,动态语言完整性更强(即不那么严格)。

image-20220604201538346

(6b)static更适合原型的方面在于:type system是一个很好的手段来记录(document)并检查(check)不断迭代的程序中数据结构和代码,代码更规整,引入一些临时的代码残段也更容易。

image-20220604201751866

(7a)dynamic更适合迭代的方面在于:在不影响使用者的情况下,能更宽松地更改代码(更不容易去修改函数中用到的各种数据结构)。

image-20220604202630149

(7b)static更适合于迭代的场合:当我们改变了某种类型或代码,type-checker会给出“to-do”list,其实就是类型检测器会检查出来哪些位置使用了被更改的类型或代码,这给程序编写者提供了一个需要修改的“to-do”list,因此能够在出bug之前修改(avoid introducing bug)。而动态类型就很难做到这一点,一旦修改了某个代码,可能被影响的位置难以明确(也许只有出bug的时候才知道)

image-20220604202926707

真就是编程哲学了,我们的实际问题不在于讨论哪种更好,而是**“我们需要什么东西静态执行”**。动静态类型有各自的代价,在需要的时候使用合适的类型检查方式。

image-20220604203424145

题外话:看看作业中的第四题,这里想要记录一下

image-20220604221617559

有四个选项大概分别是:

A.ML without change is sound …

B.ML with change is sound …

C.ML without change is complete …

D.ML with change is complete …

我一开始选了AD但错了一个选项,所以正确答案应该是AB,ML本身是sound的应该没问题,然后因为更改成为题述规则之后,ML仍然没有漏检错误的情况(理论上多一些参数应该不会造成太大的问题),虽然变得稍微complete了一些但总体上还是sound的(没有漏检的实用type checker)。应该是程度达不到complete(complete没有误报的代价是或多或少会漏检)。之后再做一遍题目,验证一下这个想法。

Optional: eval and quote

eval就是在运行时获取某种数据结构,并将这种数据结构作为一段程序,然后运行的方法。(这一节需要接在上周的内容后面一起看)。eval其实就需要有一种程序实现方式来支撑(解释、编译、或者两者结合),类似于上周的eval-exp。

image-20220604223310322

eval在Racket中的实现:

对于实现了eval的程序会更难分析,因为相当于程序中夹杂了另一段程序。如果eval计算的是Racket程序,那么实际上使用到的就是Racket的解释器(如果是JavaScript的eval计算JS程序,那么就使用JS的解释器)

image-20220604225138016

Racket中两种参与eval的数据结构,一种是带符号的list,其中的symbol就是关键词e0,列表元素就是使用的参数表达式e1…en。另外也可以用quote引用结构直接将一段Racket语句包括进来。

image-20220604225357762

Quote的语法如下。在Ruby、Python、Perl或者JS中的eval使用含有代码语句的字符串作为eval的参数来计算,实际上也是一种Quote(字符串毕竟就是“引号”表示的)

image-20220604225815888

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

Programming Languages PartB Week3学习笔记——动态还是静态?编程语言哲学 的相关文章

  • 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
  • ls-remote --tags --heads git://github.com/adobe-webplatform/eve.git

    报错日志 leepandar 64 localhost ant design vue jeecg yarn install yarn install v1 22 19 1 4 x1f50d Resolving packages 2 4 x1
  • python遇到‘\u’开头的unicode编码

    web信息中常会遇到 u4f60 u597d 类型的字符 首先 u 开头就基本表明是跟unicode编码相关的 xff0c u 后的16进制字符串是相应汉字的utf 16编码 python里decode 和encode 为我们提供了解码和编
  • gVim配色和字体选择

    本文简述如何跟换gVim的字体和选择喜欢的配色方案 xff1a 1 下载配色方案 xff1a gVim官网提供了很多配色方案 xff0c 可以根据自己的需要来选择下载 xff0c 本人比较喜欢深色背景系列的 xff0c 所以以下列举一些 x
  • Python中使用下划线定义变量的各种含义

    分类 在本文中 xff0c 我将讨论以下五种下划线模式和命名约定 xff0c 以及它们如何影响Python程序的行为 xff1a 单前导下划线 xff1a var单末尾下划线 xff1a var 双前导下划线 xff1a var双前导和末尾
  • Python 函数参数后面加冒号,函数后面跟箭头的含义

    span class token keyword def span span class token function f span span class token punctuation span text span class tok
  • “清一色”数列1,11,111,1111,...

    简单而有趣的 清一色 xff1a 1 11 111 1111 结论1 xff1a 数列中所有的项模4余3 证明 xff1a 显然可以从所有的项模2余1来思考 xff08 关于mod的一些变式 xff09 xff0c 这里用更基础的方法 每一

随机推荐