V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ech0x
V2EX  ›  编程

错误、类型和抽象

  •  
  •   ech0x · 2017-08-26 15:39:10 +08:00 · 2490 次点击
    这是一个创建于 2647 天前的主题,其中的信息可能已经有所发展或是发生改变。

    来源:我的博客 欢迎指出错误:-)

    缘起

    最近在试图学习 Haskell,这门除了赞美就是段子的语言( Lisp:论段子,不是针对谁,在座的都是**)。

    在许多介绍 Haskell 的文章中都提到的一个观点:

    “一个通过编译的 Haskell 程序是没有错误的。”

    我因此困扰了好久,在读了许多关于类型系统的文章之后才稍有拔云见日之感,此篇博文权当记录。

    错误

    “一个通过编译的 Haskell 程序是没有错误的。”

    首先这句话严格来说这句话是不正确的,一个通过编译的 Haskell 程序同样是有可能有错误的,所以寄希望 Haskell 能解决困扰你的 Bug 的同学可以放弃了,这是不可能的。永远要记得«人月神话»的主要论点

    没有银弹

    但是凡是都有起因,既然 Haskell 没法避免全部的错误,那么当我们讨论一个编译后的 Haskell 程序是正确的时候(不涉及正确性证明),我们在讨论些什么?这要从错误说起。

    首先我们得先明白我们口中的"程序错误"具体指的是什么

    我觉得程序错误可以分为以下几类

    • 语法错误
    • 语义错误
    • 逻辑错误

    语法错误很好理解,在我们日常编程的过程中会经常性的遇见这种错误,特别是对于初学者这种错误会更加的常见(比如我),例子也很好举比如漏写了分号,操作符用错了等等,这些都是语法错误。

    而语义错误可能稍微难以理解一些,语义错误指的是意思上的错误,主要指的是语句符合语义规则,但是结果不正确。例如考虑下面这个 Python 中的例子

    a = 2
    b1 = 3
    b2 = "Hello"
    c = a * b2  #实际想写的是  c = a * b1
    print(c) 
    

    如果你将这段代码直接放到解释器中去运行的话你得到的应该结果应该是"HelloHello"( String ),而我们实际想得到的是 6 ( int ),这与我们预期不符,如果我们直接将 c 作为一个参数传递的话,在接下去的程序中就有可能发生错误乃至于崩溃,所以在 Python 中为了规避这种错误就可能会用到 Try 语句或者不优雅一些的用到 type 函数。

    最后逻辑错误指的是思考的错误,这个错误应该不用多说。

    那么 Haskell 规避的是那些错误呢?

    答案应该是非常明了的,Haskell 规避了语法错误和语义错误。

    所以我们应该说,通过编译的 Haskell 程序要比别的程序更"安全"一些。

    类型

    "OK,",你也许会说,"我知道了 Haskell 会有更少的错误,但是这是怎么做到的呢,这和类型又有什么关系?"

    这要从 Haskell 的三个属性说起,Haskell 是强类型,静态(类型)的,且 Haskell 不会发生隐式的类型转换(写 javascript 的同学肯定或多或少的踩到过类型转换这个坑吧)。

    引用«Real World Haskell 中文版»几段话可以说明这三个特性是如何让 Haskell 更加安全的

    强类型的最大好处是可以让 bug 在代码实际运行之前浮现出来。比如说,在强类型的语言中,“不小心将整数当成了字符串来使用”这样的情况不可能出现。

    静态类型系统指的是,编译器可以在编译期(而不是执行期)知道每个值和表达式的类型。Haskell 编译器或解释器会察觉出类型不正确的表达式,并拒绝这些表达式的执行

    Haskell 对强类型和静态类型的双重支持使得程序不可能发生运行时类型错误,这也有助于捕捉那些轻微但难以发现的小错误,作为代价,在编程的时候就要付出更多的努力[译注:比如纠正类型错误和编写类型签名]。Haskell 社区有一种说法,一旦程序编译通过,那么这个程序的正确性就会比用其他语言来写要好得多。(一种更现实的说法是,Haskell 程序的小错误一般都很少。)

    抛开 Haskell 我们开始话题——类型。

    我觉得类型就是一种基础的抽象,让我缓缓道来原因。

    我们在编程的过程中没少跟类型系统打交道:String,Int,Char 都是说的上来的名字,那么类型系统对我们来说最大的意义是什么?为什么不可以全部都用 Object 代替呢?

    首先数据类型是一段内存的抽象,写过C的同学肯定知道,Char 代表一个字节,而 String 是 Char 构成的数组,数组是一段连续的内存,这就是对于内存的抽象。

    其次数据类型是对操作的抽象,来让我们想想当我们看见 String 类型是脑海里浮现出的有哪些操,拼接?切片?而看见 Int 类型时我们又会冒出哪些操作呢?不知各位看官浮现出来的是什么,反正我第一反应是四则运算。

    最后让我们考虑一个更为抽象的例子——数组,虽然数组的概念比起 String、Int 而言可能更为的抽象,但它所让人联想到的操作却是清晰的,切片,取长度都是数组让人联想到的操作。

    到此时我们可以做下一个粗浅的结论,数据类型是一段内存和它所容许的操作共同的抽象。

    所以不能全部用 Object 代替类型的原因也就清楚了,Obeject 无法提供必要的抽象。

    有了这个结论,我们就可以更好的理解类与基础数据类型之间的像似之处,它们都是对内存和操作的抽像,只不过类要更为复杂罢了。

    结语

    本想理清类型系统的,但写着写着发现自己学识不够,文章倒像是 Haskell 的传教了,希望大家有所得。

    2 条回复    2017-08-29 11:12:07 +08:00
    maxco292
        1
    maxco292  
       2017-08-29 11:06:24 +08:00
    其实若想编译即没有 bug,方法是有的,就是给程序写证明,如 idris,f*,这样的 Dependent type 的语言
    maxco292
        2
    maxco292  
       2017-08-29 11:12:07 +08:00
    另外,类型系统其实可以通过《类型和程序设计语言》入门
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5960 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 02:04 · PVG 10:04 · LAX 18:04 · JFK 21:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.