如何设计一门编程语言(九) 类型

类型是了解编程语言的重要一环。就算是你喜欢动态类型语言,为了想实现一个靠谱的东西,那也必须了解类型。举个简单的例子,我们都知道+和-是对称的——当然这只是我们的愿望了,在javascript里面,"1"+2和"1"-2就不是一回事。这就是由于不了解类型的操作而犯下的一些滑稽的错误。什么,你觉得因为"1"的类型是string所以"1"+2就应该是"12"?啐!"1"的类型是(string | number),这才是正确的做法。

了解编程语言的基本原理并不意味着你一定要成为一名编译器的前端,正如同学习Haskell可以让你的C++写得更好一样,如果你知道怎么设计一门语言,那遇到语言里面的坑,你十有八九可以当场看到,不会跳进去。当然了,了解编程语言的前提是你是一个优秀的程序员,至少要写程序,对吧。于是我这里推荐几门语言是在此之前要熟悉的。编程语言有好多种,每一种都有其代表作,为了开开眼界,知道编程语言可以设计成什么样子,你至少应该学会:

C++

C#

F#

Haskell

Ruby

Prolog

其实这一点也不多,因为只是学会而已,知道那些概念就好了,并不需要你成为一个精通xx语言的人。那为了了解类型你应该学会什么呢?没错——就是C++了!很多人可能不明白,为什么长得这么难看的C++竟然有这么重要的作用呢?其实如果详细了解了程序设计语言的基本原理之后,你会发现,C++在除了兼容那个可怜的C语言之外的那些东西,是设计的非常科学的。当然现在讲这些还太早,今天的重点是类型。

如果你们去看相关的书籍或者论文的话,你们会发现类型这个领域里面有相当多的莫名其妙的类型系统,或者说名词。对于第一次了解这个方面的人来说,熟练掌握Haskell和C++是很有用的,因为Haskell可以让你真正明白类型在程序里面的重要做哟的同时。几乎所有流行的东西都可以在C++里面找到,譬如说:

面向对象→class

polymorphic type→template

intersection type→union / 函数重载

dependent type→带数字的模板类型

System F→在泛型的lambda表达式里面使用decltype(看下面的例子)

sub typing的规则→泛型lambda表达式到函数指针的隐式类型转换

等等等等,因有尽有,取之不尽,用之不竭。你先别批判C++,觉得他东西多所以糟糕。事实是,只要编译器不用你写,那一门语言是不可能通过拿掉feature来使它对你来说变得更牛逼的。不知道为什么有那么多人不了解这件事情,需要重新去念一念《形式逻辑》,早日争取做一个靠谱的人。

泛型lambda表达式是C++14(没错,是14,已经基本敲定了)的内容,应该会有很多人不知道,我在这里简单地讲一下。譬如说要写一个lambda表达式来计算一个容器里所有东西的和,但是你却不知道容器和容器里面装的东西是什么。当然这种情况也不多,但是有可能你需要把这个lambda表达使用在很多地方,对吧,特别是你#include <algorithm>用了里面超好用的函数之后,这种情况就变得常见了。于是这个东西可以这么写:

auto lambda = [](const auto& xs)
{
    decltype(*xs.begin()) sum = 0;
    for(auto x : xs)
    {
        sum += x;
    }
    return sum;
};

于是你就可以这么用了:

vector<int> a = { ... };
list<float> b = { ... };
deque<double> c = { ... };

int sumA = lambda(a);
float sumB = lambda(b);
double sumC = lambda(c);

然后还可以应用sub typing的规则把这个lambda表达式转成一个函数指针。C++里面所有中括号不写东西的lambda表达式都可以被转成一个函数指针的,因为他本来就可以当成一个普通函数,只是你为了让业务逻辑更紧凑,选择把这个东西写在了你的代码里面而已:

doube(*summer)(const vector<double>&);
summer = lambda;

只要搞明白了C++之后,那些花里胡俏的类型系统的论文的概念并不难理解。他们深入研究了各种类型系统的主要原因是要做系统验证,证明这个证明那个。其实编译器的类型检查部分也可以当成是一个系统验证的程序,他要检查你的程序是不是有问题,于是首先检查系统。不过可惜的是,除了Haskell以外的其他程序语言,就算你过了类型系统检查,也不见得你的程序就是对的。当然了,对于像javascript这种动态类型就罢了还那么多坑(ruby在这里就做得很好)的语言,得通过大量的自动化测试来保证。没有类型的帮助,要写出同等质量的程序,需要花的时间要更多。什么?你不关心质量?你不要当程序员了!是因为老板催得太紧?我们Microsoft最近有招聘了,快来吧,可以慢慢写程序!

不过正因为编译器会检查类型,所以我们其实可以把一个程序用类型武装起来,使得错误的写法会变成错误的语法被检查出来了。这种事情在C++里面做尤为方便,因为它支持dependent type——好吧,就是可以在模板类型里面放一些不是类型的东西。我来举一个正常人都熟练掌握的例子——单位。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c++
, 语言
, 编程语言
, 类型
, lambda
, decltype
一个
编程语言类型、编程语言的类型、弱类型编程语言、c语言编程软件、编程语言,以便于您获取更多的相关知识。

时间: 2024-10-26 05:39:50

如何设计一门编程语言(九) 类型的相关文章

如何设计一门编程语言(二) 什么是坑(b)

我从来没有在别的语言的粉里面看见过这么容易展示人性丑陋一面的粉,就算是从十几年前开始的C++和C对喷,GC和非GC对喷,静态类型动态类型对喷的时候,甚至是云风出来喷C++黑得那么惊天动地的时候,都没有发生过这么脑残的事情.这种事情只发生在go语言的脑残粉的身上,这究竟代表什么呢?想学go语言的人最好小心一点了,学怎么用go没关系,go学成了因为受不了跳到别的语言去也没关系,就算是抖M很喜欢被折腾所以坚持用go也没关系,但是把自己学成了脑残粉,自己的心智发生不可逆转的变换,那就不好了. 当然,上一

如何设计一门编程语言(一) 什么是坑(a)

这个系列的起因是这样的,王垠写了一篇喷go的博客http://www.yinwang.org/blog-cn/2013/04/24/go-language/,里面说go已经烂到无可救药了,已经懒得说了,所以让大家去看http://www.mindomo.com/view.htm?m=8cc4f95228f942f8886106d876d1b041,里面有详细的解释.然后这篇东西被发上了微博,很多博友立刻展示了人性丑陋的一面: 1.那些go的拥护者们,因为go被喷了,就觉得自己的人格受到了侮辱一样

如何设计一门编程语言(十) 正则表达式与领域特定语言(DSL)

几个月前就一直有博友关心DSL的问题,于是我想一想,我在gac.codeplex.com里面也创建了一些DSL,于是今天就来说一说这个事情. 创建DSL恐怕是很多人第一次设计一门语言的经历,很少有人一开始上来就设计通用语言的.我自己第一次做这种事情是在高中写这个傻逼ARPG的时候了.当时做了一个超简单的脚本语言,长的就跟汇编差不多,虽然每一个指令都写成了调用函数的形态.虽然这个游戏需要脚本在剧情里面控制一些人物的走动什么的,但是所幸并不复杂,于是还是完成了任务.一眨眼10年过去了,现在在写Gac

如何设计一门编程语言(五) 面向对象和消息发送

面向对象这个抽象的特例总是有说不完的话题,更糟糕的是很多语言都错误地实现了面向对象--class居然可以当一个变量类型什么的这只是让人们写代码写的更糟糕而已.当然这个话题第三篇文章已经说过了,现在来谈谈人们喜欢拿来装逼的另一个话题--消息发送. 按照惯例先来点题外话.说到消息发送,有些人喜欢跳出来说,objective-c的消息做得多优雅啊,代码都可以写成一句话[golang screw:you you:suck]之类的.其实这个还做得不够彻底.在几年前易语言曾经火了一阵,但是为什么大家这么讨厌

如何设计一门编程语言(八) 异步编程和CPS变换

关于这个话题,其实在(六)里面已经讨论了一半了.学过Haskell的都知道,这个世界上很多东西都可以用monad和comonad来把一些复杂的代码给抽象成简单的.一看就懂的形式.他们的区别,就像用js做一个复杂的带着几层循环的动画,直接写出来和用jquery的"回调"写出来的代码一样.前者能看不能用,后者能用不能看.那有没有什么又能用又能看的呢?我目前只能在Haskell.C#和F#里面看到.至于说为什么,当然是因为他们都支持了monad和comonad.只不过C#作为一门不把&quo

如何设计一门编程语言(三) 什么是坑(面向对象和异常处理)

在所有的文字之前,我需要强调一下,我本人对structure typing持反对态度,所以就算文中的内容"看起来很像"go的interface,读者们也最好不要觉得我是在赞扬go的interface.我比较喜欢的是haskell和rust的那种手法.可惜rust跟go一样恨不得把所有的单词都缩成最短,结果代码写出来连可读性都没有了,单词都变成了符号.如果rust把那乱七八糟的指针设计和go的那种屎缩写一起干掉的话,我一定会很喜欢rust的.同理,COM这个东西设计得真是太他妈正确了,简

如何设计一门编程语言(十一) 删减语言的功能

大家看到这个标题肯定会欢呼雀跃了,以为功能少的语言就容易学.其实完全不是这样的.功能少的语言如果还适用范围广,那所有的概念必定是正交的,最后就会变得跟数学一样.数学的概念很正交吧,正交的东西都特别抽象,一点都不直观的.不信?出门转左看Haskell,还有抽象代数.因此删减语言的功能是需要高超的技巧的,这跟大家想的,还有跟go那帮人想的,可以断定完全不一样. 首先,我们要知道到底为什么需要删减功能.在这里我们首先要达成一个共识--人都是很贱的.一方面在发表言论的时候光面堂皇的表示,要以需求变更和可

如何设计一门编程语言(四) 什么是坑(操作模板)

其实我在写这个系列的第三篇文章的时候就已经发现,距离机器越远,也就是抽象越高的概念,坑的数量是越少的.但是这并不是说,距离机器越近的概念就越强大或者说越接近本质.这是广大的程序员对计算理论的一种误解.大多数人理解编程的知识结构的时候,都是用还原论来理解的,这个方法其实并没有错.但问题在于,"还原"的方法并不是唯一的.很多人觉得,反正你多高级的语言编译完了无非都是机器码嘛.但是还有另一种解释,你无论多低级的语言编译完了无非也就是带CPS变换(continuation passing st

如何设计一门编程语言(七) 闭包、lambda和interface

人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是闭包.不过因为系列文章主题的缘故,在这里我就跟大家讲一下闭包是什么东西.在理解闭包之前,我们得先理解一些常见的argument passing和symbol resolving的规则. 首先第一个就是call by value了.这个规则我们大家都很熟悉,因为流行的语言都是这么做的.大家还记得刚开始