zoukankan      html  css  js  c++  java
  • 冒号课堂§5.2:数据类型

    冒号课堂

    第五课 语言小谈(2)

    5.2数据类型——规则与变通

    操纵于规矩之中,神明于规矩之外                        ——《俞震·古今医案按》

     

    关键词:  数据类型,静态类型,动态类型,Duck类型,强类型,弱类型,类型安全

    摘要:   关于数据类型的讨论

     

    预览 

    ·           Duck类型的哲学是:名义不重要,重要的是能力

    ·           将一个会叫会游的家伙放进池塘看起来不算坏主意,但如果一艘轮船趁机也轰隆隆地开了进来,事情恐怕就不那么美妙了

    ·           静态类型检查类似“疑罪从有”的有罪推定制,动态类型检查类似“疑罪从无”的无罪推定制

    ·           尽可能守规则,必要时求变通

    ·           规则如裤带,过于宽松和过于束缚都不好

     

    提问 

    ·           动态语言与动态类型语言是一回事吗?

    ·           数据类型有哪两个要素?其意义何在?

    ·           什么是动态类型和静态类型?它们的区别是什么?各有什么优缺点?

    ·           什么是鸭子类型(duck typing)?它有什么优缺点?

    ·           什么是强类型与弱类型?什么是类型安全的?

     

    讲解  

    待教室平静下来,冒号再度开腔:“在谈论动态语言之前,最好先澄清一下它与动态类型语言之间的区别。”

    叹号讶然道:“它们不是一回事吗?一直以为动态语言是动态类型语言的简称呢。”

    “有亲戚之名,却无血缘之亲。名称上相似,加之动态语言绝大多数确是动态类型语言,造成混淆实属在所难免,但二者之间并无必然联系——动态语言不一定是动态类型语言[1],动态类型语言也不一定是动态语言[2]。”冒号飞跑的舌头几乎绊蒜,同时把众人的脑子搅成了一锅粥。

    见势不妙,冒号改用迂回战术:“我们不妨再谈开些,大家对数据类型是如何理解的?”

    逗号随口道:“数据类型不就是数据的种类吗?”

    众人暗笑:说了跟没说差不多。

    冒号说道:“数据类型包含两个要素:一个是允许取值的集合,一个是允许参与的运算。例如int类型在Java中既定义了介于− 231 231 − 1之间的整数集合,也定义了该集合上的整数所能进行的运算。现在的问题是:数据类型的意义何在?”

    句号回答:“限定一个变量的数据类型,就意味着限制了该变量的取值范围和所参与的运算,这从一定程度上保证了代码的安全性。”

    冒号追问:“还有吗?”

    句号略作思考后说:“用户自定义的数据类型,如C中的结构和Java中的类或接口,赋予数据以逻辑内涵,提高了代码的抽象性。”

    “精辟!”冒号赞道,“数据类型既有针对机器的物理意义,又有针对人的逻辑意义。前者用于进行底层的内存分配和数值运算等,后者用于表达高层的逻辑概念。既然类型如此重要,类型检查就必不可少了[3]。所谓动态类型语言dynamic typing language),正是指类型检查发生在运行期间run-time)的语言。”

    “那静态类型语言static typing language)自然是类型检查发生在编译期间compile-time)的语言咯。”引号接话道。

    冒号回应:“一般的说法是这样,但我更愿意将‘编译期间’四个字改为‘运行之前’,否则容易让人误解为静态类型语言一定是编译型语言(compiled language)。”

    问号问道:“是否可以这么说:静态类型语言需要变量声明,而动态类型语言则不需要?”

    “这话只对了一半。”冒号评论,“动态类型语言固然不需要显式的变量声明(explicit declaration,一些静态类型语言有时也不需要。典型的如ML、Haskell之类的函数式语言,编译器可以通过上下文来进行类型推断(type inference)。另外,C#从3.0起也开始支持局部变量的类型推断。”

    “如何进行类型推断?”问号有点丈二和尚摸不着头脑。

    冒号打了个比方:“假设‘+’号只限于同类型的数据运算,那么从表达式a + 1中可以推出a是整型变量,从b + 1.0中推出b是浮点型变量,从c + “1”中推出c是字符串型变量。这些变量不必事先声明,但一旦类型被推断确定后,便不再更改。由于这些推断都是在程序运行之前进行的,因此仍属于静态类型。它既有动态类型的简洁性,又不失声明式静态类型的安全性,可谓裁长补短啊。”

    叹号有些羡慕地说:“还是动态类型语言好,不仅不必声明变量,而且一个变量在不同地方还可以代表不同类型,多省事多方便啊!”

    冒号微微颔首:“虽然这种机制也有为人诟病之处,但不可否认,动态类型语言的确有它的优势:简明、快捷、灵活,并且天然具有泛型(generic)特征。值得一提的是,动态类型有一种被称作鸭子类型(duck typing)的形式。”

    逗号感到有趣:“鸭子类型?很滑稽的名字。”

    “这种类型通俗的说法是:如果一个对象既会走鸭步又会呷呷叫,何妨将其视作鸭子呢?”冒号说着投影出一段Ruby代码——

    class Duck                        #会叫会游的鸭

        def shout

            puts '呷呷呷'

        end

        def swim

            puts '鸭泳'

        end

    end

     

    class Frog                         #会叫会游的蛙

        def shout

            puts '呱呱呱'

        end

        def swim

           puts '蛙泳'

        end

    end

     

    def shoutAndSwim(duck)   #让一只会叫会游的家伙边叫边游

        duck.shout

        duck.swim

    end

     

    shoutAndSwim(Duck.new)   #让一只鸭边叫边游

    shoutAndSwim(Frog.new)   #让一只蛙边叫边游

     

    冒号继续讲解:“在Smalltalk、Python和Ruby等动态类型的OOP语言中,只要一个类型具有shout和swim的方法,它就可以为shoutAndSwim所接受。这在C++、Java、C#等静态类型语言中是不可能的[4],除非鸭和蛙在同一继承树上,或者二者均显式实现了一个包含shout和swim的公用接口。”

    句号敏锐地指出:“C++是静态类型语言,但它的模板也可实现类似功能,并不需要引入继承关系。”

    “说得很对!但请接着看下去。”冒号又放出一段投影——

    class Cock                                 #会叫不会游的鸡

        def shout

            puts '喔喔喔'

        end

    end

     

    class Fish                               #会游不会叫的鱼

        def swim

            puts '自由泳'

        end

    end

     

    def shoutOrSwim(duck, flag)   #让一只会叫或会游的家伙叫或游

        flag ? duck.shout : duck.swim

    end

     

    shoutOrSwim(Cock.new, true)       #让一只鸡叫

    shoutOrSwim(Fish.new, false)       #让一只鱼游

     

    “这里鸡没有swim的方法,鱼没有shout的方法。若采用C++的模板,shoutOrSwim是无法通过编译的。但在支持Duck 类型的语言中,只要在运行期间不让鸡swim、让鱼shout——除非你突发奇想——一切平安无事。”冒号作了个OK的手势。

    “动态类型语言真是越看越可爱。”叹号简直垂涎欲滴了。

    “Duck类型的哲学是:名义不重要,重要的是能力,颇有些实用主义的味道。这种非继承性多态为软件重用开启了新的窗口,同时也埋下了一些陷阱。由于Duck类型的接口组合是隐性的,其使用者需要比普通interface更小心以避免误用;其维护者也需要更小心以避免破坏客户代码;另外它也可能造成滥用——将一个会叫会游的家伙放进池塘看起来不算坏主意,但如果一艘轮船趁机也轰隆隆地开了进来,事情恐怕就不那么美妙了。”

    众皆莞尔。

    “再来看看静态类型语言的好处:由于在运行之前进行了类型检查,一方面代码的可靠性增强,符合‘发现错误要尽早’的原则;另一方面编译器有可能藉此优化机器代码以提高运行效率,同时相比前者节省了运行期的耗费在类型检查上的时间和空间。此外,变量类型的声明彰显了编程者的意图,有辅助文档的功效。”冒号有条有理地解释着,“两种类型的体制可以用两种法律原则来类比:静态类型检查类似‘疑罪从有’的有罪推定制——在被证明合法之前是非法的,动态类型检查类似‘疑罪从无’的无罪推定制——在被证明非法之前是合法的。至于如何取舍,套用一句话:‘Static Typing Where Possible, Dynamic Typing When Needed’。不妨理解为:尽可能守规则,必要时求变通。”

    句号俏皮地说:“规则如裤带,过于宽松和过于束缚都不好。”

    问号提出新问题:“动态类型语言与弱类型语言有何不同?”

    冒号喟言:“它们也常常被混为一谈,但类型的动静与强弱完全是正交的两个概念。静态类型语言中,有强类型的Java,也有弱类型的C;动态类型语言中,有强类型的Smalltalk,也有弱类型的JavaScript。前者以类型的绑定(binding)时间来划分,后者以类型的约束强度来划分。通常弱类型语言weakly-typing language)允许一种类型的值隐性转化为另一种类型[5]。举个例子,1"2"VB中等于3——第二个字符串转化为整数;在JavaScript中等于"12"——第一个整数转化为字符串;在C中则等于一个不定的整数值——第二个字符串作为地址来运算。这样似乎很有趣很方便,但程序容易藏污纳垢,滋生臭虫(bug)。与此相对地,强类型语言strongly-typed language)着意贯彻类型控制,为保障数据的完整性和代码的安全有效性,一般不允许隐性类型转换[6]。如果一定需要类型转换,必须是显性转换,一般通过我们熟知的铸型cast)来完成。”

    引号想起:“好像还有一种所谓的类型安全语言?”

    逗号紧紧抱着头,仿佛害怕裂开。

    “类型按安全性来划分,可分为类型安全语言(type-safe language)和类型不安全语言(type-unsafe language)。类型检查的目的就是为了避免类型错误(type error[7],即杜绝因类型问题而产生的错误或不良代码。如果一个类型系统能完全做到这一点,它就被称为类型安全的。虽然尚存争议,但一般认为强类型语言对类型控制更严格,因而是类型安全的,弱类型语言是类型不安全的。类型安全固然对保障程序的合理性和可靠性十分重要,但若过于严苛,程序也就失去了活力,正所谓‘水至清则无鱼’啊。” 冒号有条不紊地解说着,“至此,我们已论及数据类型的三种划分方式。需要说明的是,这些划分并非泾渭分明的[8],更多的是定性而非定量的描述,甚至没有公认统一的定义。但了解它们,对我们理解编程语言和编程原则是大有裨益的。

     

    插语 

    [1] Scala是动态语言,却是静态类型的。

    [2] Visual Basic(不包括VB.NET) 支持动态类型,却是静态语言。

    [3] 极少数语言没有类型检查(untypedtypeless),如大多数汇编语言、Forth语言等。

    [4] C#4.0将支持duck typing

    [5] 隐式转换也称为强制转换coercion)。有人将显式转换的铸型cast)译为强制转换,并不准确。

    [6] 但许多强类型语言对于宽转换widening conversion)还是允许隐性的,如必要时int可自动转换为float

    [7] 典型的类型错误是:一个函数本来期待的参数类型是A,实际传入的变量a却不是A类型或其兼容类型。

    [8] 比如,静态类型的OOP语言如C++JavaC#等支持多态类型以及downcasting,能在运行期间进一步细化数据类型,从某种意义上也具有动态类型的特征。

     

     总结
    • 尽管动态语言大多数是动态类型语言,但二者并不是一回事。
    • 数据类型包含两个要素:允许取值的集合和允许参与的运算。
    • 数据类型既有针对机器的物理意义,又有针对人的逻辑意义,提高了代码的安全性和抽象性。
    • 动态类型的类型检查发生在运行期间,静态类型的类型检查发生在编译期间(运行之前)。
    • 动态类型的变量不需要显式声明,静态类型的变量需要通过显式声明或类型推断。
    • 鸭子类型是动态类型的一种风格,允许非继承性多态,即一个对象的类型可以由其接口集合来确定,不需要通过显式继承。它有利于代码重用,但也可能造成误用和滥用。
    • 动态类型语言的优点:代码简明灵活、易于重用,适合泛型编程和快速原型开发。
    • 静态类型语言的优点:运行之前的类型检查增强了代码的可靠性,使编译器有可能进行优化处理从而提高运行效率,节省了运行期的类型检查所占用的时间和空间,同时类型声明有辅助文档的功效。
    • 静态类型检查实行“疑罪从有”的有罪推定制,动态类型检查实行“疑罪从无”的无罪推定制。取舍的原则是:Static Typing Where Possible, Dynamic Typing When Needed。即尽可能守规则,必要时求变通。
    • 类型的动静以类型的绑定时间来划分,类型的强弱以类型的约束强度来划分,它们之间没有必然联系。弱类型语言允许类型的隐性转化,被认为是类型不安全的;而强类型语言则一般不允许这种转化,被认为是类型安全的。

     

    “”参考

    [1] WikipediaType systemhttp://en.wikipedia.org/wiki/Type_system

    [2] Erik MeijerPeter DraytonStatic Typing Where Possible, Dynamic Typing When Neededhttp://research.microsoft.com/~emeijer/Papers/RDL04Meijer.pdf

    [3] Ravi SethiProgramming Languages: Concepts & Constructs(英文版第2).北京:机械工业出版社,2002136-143

  • 相关阅读:
    js对象数组(JSON) 根据某个共同字段 分组
    一个 函数 用来转化esSearch 的range 条件
    关于 vuex 报错 Do not mutate vuex store state outside mutation handlers.
    android listview 重用view导致的选择混乱问题
    android SDK和ADT的更新
    Android中adb push和adb install的使用区别
    pycharm中添加扩展工具pylint
    su Authentication failure解决
    Putty以及adb网络调试
    有关android源码编译的几个问题
  • 原文地址:https://www.cnblogs.com/xyz98/p/1444394.html
Copyright © 2011-2022 走看看