精华内容
下载资源
问答
  • 最流行的语言中如Java、C和C++的排名没有变化,从10名之外进入前10的两种语言是Perl和汇编语言,像汇编语言这种最低级语言流行起来有些出人意料。一种解释是能运行汇编语言的设备在增加,你的电动牙刷和咖啡机今天都...

    最新的TIOBE语言流行度指数显示汇编语言再次进入前10。最流行的语言中如Java、C和C++的排名没有变化,从10名之外进入前10的两种语言是Perl和汇编语言,像汇编语言这种最低级语言流行起来有些出人意料。一种解释是能运行汇编语言的设备在增加,你的电动牙刷和咖啡机今天都能运行汇编代码。

    有些人认为“JavaScript是Web的汇编语言”完全是精神病说的话。为此,我询问了几位JavaScript权威,比如Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),还有Mike Shaver(Mozilla技术副总裁)。以下都是从个人邮件里摘过来的,得到了以上几位的许可。

    有些人认为“JavaScript是Web的汇编语言”完全是精神病说的话。为此,我询问了几位JavaScript权威,比如Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),还有Mike Shaver(Mozilla技术副总裁)。

    编程语言社区TIBOE最新公布了2016年7月的编程语言排行,排名前十位的编程语言分别是Java、C、C++、Python、C#、PHP、JavaScript、VB.NET、Perl、Assembly Language(汇编语言)。相对于其他高级语言来讲,汇编这门底层编程语言再次入围排行榜前十让不少人吃惊不已。

    TIBOE编程语言社区公布了2016年7月最新一期的编程语言排行榜,Java凭借19.8%的占有率继续遥遥领先,C、C++排名第2、第3。苹果专用编程语言Objective-C下滑严重,从去年第6下滑到了15名。具体来讲,在最新一期的TIBOE排行榜中,排名前十位的编程语言分别是Java、C、C++、Python、C#、PHP、JavaScript、VB.NET、Perl、Assembly Language(汇编语言)。而让人意外的是,“老掉牙”的汇编语言,居然再次进入前十(排名第10)。分析认为,汇

    得益于法律法规的支撑,电子印章已经在组织的各类业务中得到广泛应用:『今年 4 月国务院令明确规定可靠的电子签名与手写签名或者盖章具有同等法律效力,《电子签名法》的修改进一步放宽了电子签名使用范围。目前,除了涉及婚姻、收养、继承等人身关系的;涉及停止供水、供热、供气等公用事业服务的;以及法律、行政法规规定等不适用以外,其余业务全部普及电子印章。』现在:契约锁电子印章在为全国各大企事业单位、政府机关客户?

    中国互联网协会主办的2018(第十七届)中国互联网大会在北京国家会议中心开幕,大会以“融合发展协同共治——新时代 新征程 新动能”为主题。此次大会的分论坛之一--“2018防范打击通讯信息诈骗论坛”在工业和信息化部网络安全管理局指导下召开,工业和信息化部网络安全管理局、公安部刑事侦查局、中国人民银行支付结算司等政府部门负责领导及中国信息通信研究院、国家互联网应急中心、各基础电信企业相关部门负责人和部分互联网企?

    阿里巴巴集团与阿里巴巴网络有限公司2月21日联合宣布,阿里巴巴集团(“要约人”)向阿里巴巴网络有限公司董事会提出私有化要约。阿里巴巴集团及其一致行动人目前持有此上市公司73.5%的股份。

    以下国外 CMS 资源全部来自全球顶尖 CMS 行业中立的研究机构,CMS 行业门户,开源 CMS 系统行业门户,包括 CMS 最新研究报告,产品分析报告,CMS产品导购指南,CMS 行业新闻,动态,以及最新技术趋势。

    随着人工智能时代的来临,编程语言的热度居高不下,随着智能机器人的出现,在2020年更是迎来一波新的高峰。在这样的大环境下,关于谁是人工智能最流行编程语言的讨论也十分热烈,C、C++、Java、Python等编程语言不断被拿出来做比较。那么接下来,风变编程来解析一波当下编程语言的新趋势。Python受欢迎度有望超越Java?近日,TIOBE编程语言社区公布了2020年10月编程语言排行榜。榜单数据显示,Python的受欢迎程度十分逼近排在第二?

    2019年8月底,华为方舟编译器(OpenArkCompiler)正式开源,迈出了跨越性的一步。一年多来,方舟编程体系陆续实现了编译器、引擎、调试器的开源,其中编译器的重点功能主要集中在Java应用程序静

    十一期间,国内旅游超6亿人次,如此海量的人群跨越山水,去往不同的城市和地域,把所见所闻的体验,又全部生成海量的社交内容。据抖音发布的「2020国庆中秋假期数据报告」显示,上海问鼎今年抖音热门旅游城市榜首,相关的旅游视频点赞数超4515万,累计播放次数超14.5亿,重庆和深圳分别位列二三。除此外还有新上榜的黑马城市苏州和东莞。两个城市相关的旅游视频点赞量也分别高达2761万和1784万,播放量分别突破10亿和6亿。可见每一

    语言康复行业发展的关键标志之一,就是言语治疗师数量与专业化水平的提升。为共同推进国内语言康复专业人员的在职培训,近期,中国听力语言康复研究中心(简称:中语康)联合北大医疗脑健康开展了关于儿童言语语言治疗师的专业技能培训,旨在借助双方专业力量,夯实语言康复的专业建设,探讨并建立国内言语治疗师的认证和考核标准。我国言语治疗师人才极度匮乏。近年来,我国言语障碍患者数量日趋增长。据今年国际聋人日发布的数据

    谷歌在其云平台上提供的功能即服务(FaaS)是云功能,允许开发者构建与第三方服务和api或物联网后端集成的无服务器解决方案。最近,这家公共云供应商宣布了一些云功能的新功能,如更多的语言支持、改进的开发体验、每个功能标识的增强安全性以及更多地区的可用性。谷歌在2018年的下一届云大会上宣布,自其全面可用以来,谷歌继续通过新的更新来增强云功能。这些更新包括对Java、Go、Node.js、还有python。现在,公司进一步扩展了?

    ​原神中是有一个语言交流的成就可以完成,叫做“...odomu”,成就的需求是在语言交流中与丘丘人交谈成功,很多玩家还不清楚这个语言交流的任务怎么做,下面就来为大家详细的介绍一下。

    随着时间的流逝,程序员们发现了更新、更简单的工作方式,新的编程语言如雨后春笋般出现,但只有少数编程语言能成为社区的新宠。这种进步的一个副作用是一些古老的编程语言必然会跟历史一样被人们遗忘。如果一个编程语言无法随着时间的推移提升其价值,那么它的用户群终将会流失,并逐渐淡出人们的视线,或者成为更新一代编程语言的基础。

    Python是一种跨平台的计算机程序设计语言。它结合了解释性、编译性、互动性和面向对象的诸多特性,最初设计用于编写自动化脚本,然而随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。近些年,Python尤其在云计算,人工智能(AI)领域,应用十分广泛。不过最近Python爆出了比较严重的注入漏洞,需要尽快升级。以下是漏洞详情:漏洞详情CVE ID: CVE-2020-26116 CVSS评分: 5.0 中Python http.client(Pytho

    最不受欢迎 / 最令人畏惧的编程语言有哪些?这些编程语言为什么令人畏惧?对它们的评价是否公正?在 StackOverflow 的 2020 年度开发者调查中,有一张表格,显示的是“最受欢迎、最令人畏惧和最想要的编程语言”。最受欢迎的和最想要的编程语言,嗯,是有点无聊。倒是那个最令人畏惧的就有意思多了。正如托尔斯泰(Tolstoy)所说的:“幸福的家庭都是相似的,而不幸的家庭则各有各的不幸。”

    TIOBE 公布 2020 年 9 月的编程语言排行榜,C++ 位列第四,仅次于 C、Java 和 Python。而且 C++ 相比去年同期增长 1.48%,成为增长最快的编程语言。

    研究表明,婴儿能区分所有语言的所有发音,而成年人却做不到这一点,成年人只能区分自己语言的声音,分不清楚外语的声音。辨音能力随着年龄的增长会逐渐退化,想要让英语启蒙得到更好的效果,就应该抓住语言敏感期,在口腔肌肉没有形成母语发音习惯的时候因时顺势输入,可以达到事半功倍的效果。九月份开学季已经到了,相信家长们一定深有体会,知道零基础学习英语的压力有多大。从小抓住孩子的黄金英语启蒙期是一件相当重要的事,

    从方舟编译器到鸿蒙系统,华为在自研的道路上越走越远。日前,有数码博主爆料:从华为内部得知,华为正在自研编程语言仓颉,该项目已经进行了很久,预计明年会向外公布具体细节。

    回归语言的交流本质,是开言英语作为成人英语领域的知名品牌,通过多年的探索总结出了一套自己的教学方式。主打北美外教脱口秀教学+实用英语付费课程的开言英语,致力于“让学员接触地道的,能用于日常沟通的英语。”通过沉浸式的场景化学习氛围,以每名学员的兴趣为出发点,定制个性化的课程内容,逐步提升大家的英语口语表达能力。 一般采用的都是模拟真实场景对话,提倡场景化的学习,让语言学习在真实的情景下、语境中进行。正

    形势逼迫之下,华为似乎不得不从硬件到软件都自己研发,近日的开发者大会上也接连公布了鸿蒙2.0系统,以及HMS生态、方舟编译器的最新进展。据一直曝料靠谱华为内幕的微博博主@长安数码君 从华

    在今天的华为HDC 2020开发者大会上,除了鸿蒙2.0系统之外,华为还推出了方舟编译器2.0,这是去年首发方舟编译器之后的升级版。华为表示,编译器开发非常难,它是处理软件的软件,因此也非常重

    9月10日,苹果在新加坡的第三家零售店正式开业,这也是苹果首家水上商店,该门店位于新加坡滨海湾金沙购物中心,目前需要通过水底通道进店,开业首天的到访者必须在官网预约才能进入。

    8月25日消息,自然语言理解(NLP)素有“人工智能皇冠上的明珠”盛誉,语言与知识技术是人工智能认知能力的核心。在今天举办的百度大脑语言与知识技术峰会上,百度集团副总裁吴甜接续发布语义理解技术与平台文心(ERNIE)、智能文档分析平台TextMind和AI同传会议解决方案3大新产品,同时发布了6项升级,包括智能创作平台的3个场景方案、以及智能对话定制与服务平台UNIT的3项全新升级。产品覆盖语言理解、语言生成、对?

    开言英语是一款主打北美主播情景对话式英语学习app。一般采用的都是模拟真实场景对话,提倡场景化的学习,让语言学习在真实的情景下进行。力求通过营造“母语”环境的练习氛围,帮助高校学生、职场白领等对英语口语有需求的人群,掌握地道的英语表达方式。 开言的课程内容涵盖日常生活、商务、旅游等多个实用场景。到现在,已经建立了一套包含了50多个英语常见场景专辑,500多项针对场景的英语核心能力知识点的学习系统。这一系统

    近日,日本公司“甜甜圈机器人”研发出了一种智能口罩,可将佩戴者说的话翻译成 8 种不同语言。这款名为C-FACE的智能口罩可戴在普通口罩外面,将佩戴者说的话从日语翻译成越南语、英语、西班牙语、汉语、韩语、泰语、印尼语和法语。

    由于新冠疫情,口罩已经成为了一种日常用品。而日本似乎十分热衷于研究不同类型的口罩,例如为了应对夏季的“冰镇口罩”和带微型电扇的口罩,还有为了女性美观研发的“小脸美口罩

    展开全文
  • 这是一部史诗般的剧集,深入探讨了Go语言的历史和细节,以及Go语言的方式和原因,以及他们在创建这种出色的编程语言过程中所做的选择。 卡门·安多(CARMEN ANDOH) 欢迎大家,去时间!今天我们为您举...

    卡门(Carmen)和乔恩(Jon)与罗​​布·派克(Rob Pike)和罗伯特·格里塞梅尔(Robert Griesemer)(Go的创造者)讨论了它的起源,增长,影响力和未来。这是一部史诗般的剧集,深入探讨了Go语言的历史和细节,以及Go语言的方式和原因,以及他们在创建这种出色的编程语言过程中所做的选择。

    卡门·安多(Carmen Andoh)说

    卡门·安多(CARMEN ANDOH)

    欢迎大家,去时间!今天我们为您举办一场非常特别的表演。今天是第100集。呜呜!我们有一些很棒的客人。今天的主持人是卡门·安多(Carmen Andoh,我,我和我),以及乔恩·卡尔霍恩(Jon Calhoun)。

    大家好!

    今天我们的两位嘉宾是Go编程语言的创建者Rob Pike和Robert Griesemer。欢迎您,我们很荣幸有您!

    大!感谢您邀请我们。

    肯也应该在这里,但他正在希腊度假,所以……他赢了。

    (笑)对。第三个–我试图弄个帽子戏法,然后…是的,他说他有很好的借口,他正在希腊度假。我们希望自己首先在希腊,但是我们很高兴能获得GoTime的安慰奖……[笑]也许吧。也许不吧。

    显然,我们的预算不允许我们所有人飞往希腊。

    是的,那太酷了。

    你问了吗?

    不,我应该有,但我认为预算不会允许这样做。预算很小。

    你好,罗伯特。

    大家好。很高兴来到这里。

    好吧,让我们开始吧。让我们谈谈Go。我想人们想知道的第一件事就是,在初期,当您决定“嘿,让我们开始编写一种编程语言”时,它是什么样的。

    罗伯特,我想这是我的错,对吧?我不确定它是如何开始的,但是我们想讲的故事是我们刚刚看到有关新版本,新版本的C ++的讨论,这是大多数服务器软件的编写语言。 Google…而且我一直在思考C ++的不合适之处,因为它缺乏对我们正在获得的新型多核计算机的支持,以及我想如何回到多年以前探索的一些想法。并发编程…然后我们坐在一起–罗伯特和我共用一个办公室,在2007年9月的某个时候,我想我真的把椅子转给了罗伯特,然后我说:“嘿,罗伯特,我们应该为此做些事情。”

    我们聊了几分钟,然后肯在下一个办公室,所以我跑去找肯说:“你想帮忙吗?”他说是的,就是这样。罗伯特,这让您记忆犹新吗?

    是的,所以我认为C ++可能要晚一点了-我不确定100%-但肯定是在9月。我昨天看了看我的笔记,我认为那一定是星期五下午,或者也许是前一天,因为我们在其中一个下午进行了集思广益的会议中有一个三小时的会议室。我的记忆有些不同。我认为您正在开发一个非常令人沮丧的C ++程序,并且又遇到了几分钟的编译时暂停,然后…

    00:04:22.21 ] 45分钟。

    好吧,45分钟,你并不特别高兴。我们中的一个人说:“我们应该停止这样做或抱怨或采取任何其他措施,并尝试对此做某事。”我想我们俩都或多或少立即决定“是的,我们应该对此真正采取行动。”

    是的,那庞大的构建也是其中的一部分,而我试图做的是处理这样一个事实,即我不允许使用线程来解决程序中的并发问题,因为在这种情况下C ++库无法正常工作方式,并且样式规则禁止在二进制文件中使用线程。所以我当时在做体操,这很容易做错,做一件让我感到震惊的事情很简单……然后,每当我碰到任何东西时,我都必须等待45分钟才能在庞大的分布式编译集群上进行另一次构建。在某个时候,我的士气刚好崩溃。我们不得不做点什么……但是我清楚地记得转过椅子说:“罗伯特,救命!”

    每当你们开始时,您是立即投入全职学习,还是像20%类型的项目或其他东西?因为我想对于大多数人来说,仅仅放弃他们正在做的事情而去学习一种语言是非常困难的。这是一项艰巨的任务。那么那是什么-只是部分地“让我每个星期五都在这20%上工作”还是其他?

    我想我们关上门开始聊天。在那之前,我实际上已经考虑了一些语言问题。我以前用其他语言工作过。我有很多我从未写下过的想法,但是它们已经在我脑海里呆了几年了。我一直在想这个问题……并不是真的在考虑做些什么,更像是一个私人宠物项目。

    对我来说,绝对不可能只做另一个项目,因为我实际上刚刚开始了另一个新项目,这是Google正在为Chrome进行的即将到来的新Javascript实现的V8解释器……因此,实际上,我试图将其压缩,直到最终设法让我的经理接受这样一个事实,即也许我想做其他事情。

    我们绝对仍然有真正的工作,因此我们不得不将其压缩。。但是我必须说,当时我们的老板-至少是我的老板和Ken的老板-当时从贝尔实验室跟我们一起来的比尔·科夫兰(Bill Coughran)是在早期提供了极大的支持,使我们可以自由地在此方面做大量的时间,并且不得不为我们认为应该做其他事情的人辩护几次。但是大约在六个月到一年之后,我想我们都全职了。

    是的,是的。我同意罗布在这里的评估。我们非常感谢Bill Coughran,因为如果他没有给我们这样做的余地,这可能就不会发生。

    Bill在贝尔实验室与您同在,并且已经与您在贝尔实验室的其他事业合作过,所以他有点理解您的能力以及如果任由自己创造的东西。设备。

    是的,比尔是我有过的最好的经理。他和我在1980年相隔一两个星期才加入贝尔实验室,所以我们彼此非常了解。我们俩都在那里的计算机科学研究中心工作了20多年。在某个时候,他升任该中心主任。我不记得他当导演时的确切角色,但是我们确实在那里进行了一个大型项目。计划9是在比尔(Bill)的支持下提出的,诸如此类,还有其他一些内部联网项目。

    00:08:23.04 ]因此,我们已经与他一起担任经理很多工作,我非常努力地招募他加入Google,因为他们需要像Bill这样的人,而我希望像Bill这样的人成为我的经理。是的,他是其中的重要组成部分。我认为,在这些故事中,人们常常忽略了合适的人帮助做某事而不真正参与其中的重要性,而比尔真的很擅长。这就是为什么他是如此出色的经理。

    那很棒。所以-

    前进。

    我只是要多补充一点时间表。因此,到2008年4月,肯一直在或想要从事一个编译器的工作。第一个是编译为C代码,然后我们使用C编译器进行编译,因为它更容易上手……尽管这种持续时间不是很长。我想是在2008年4月-当时我在悉尼,我想罗伯特当时来到悉尼,我们有一间会议室,视频通话专职设在肯的办公室,而肯的办公室仍在加利福尼亚,我们三个人一起编写了规范并实现了编译器。我想,Ken在编译器上工作,而我在一个规范上工作,来回一周或两周。

    但这是规范发生的时间。是的,几个星期。因此,我们真的开始了大约六个月的头脑风暴和大致塑形。我们所做的第一批重要工作之一-也许是我们所做的第一项重要工作-是我们编写了语言的正式规范,我认为这是项目成功的关键部分。

    那就对了。

    其中最重要的事情之一是伊恩·泰勒(Ian Taylor),他也是Google的一员,看到了规范,并决定他要为此编写一个编译器。因此,有一天他走进我们的办公室,说道:“哦,顺便说一句,我已经为您的语言编写了一个编译器。”对于我们来说,这真是令人惊讶的时刻。当然,他已成为团队的一员,他现在仍在从事Go的研究。

    是的,那完全是出乎意料的。因此,在悉尼,我认为我们已经写下了很多文字,但不是很正式,如果我没记错的话,我们花了很多时间试图弄清楚如何正确绘制地图。我们想以某种方式将地图映射到该语言中,但我们不太了解该怎么做,我认为是您,Rob,他最终说:“我们应该努力使它们在90%的情况下都能正常工作,对于其他情况,我们可能不应该使事情变得更复杂。”我认为,事后看来这是一个非常好的决定。

    我不记得了,但是听起来像我。我们还努力使数组正常工作,最终变成了切片。

    对。我认为那花了一点时间。

    是的 而且我认为切片是在我住院的时候发生的……因为几个月后我发生了一次严重事故,并且住院了一段时间。当我出来的时候,我认为那是片刻的事情。我不参与其中,但是我对结果感到非常满意。

    切成薄片–我认为一些关键思想是肯在那里的思想。

    是的,那是绝对正确的。

    因此,当您说这些事情很难弄清楚时,是因为您曾经看到过其他语言以一种您认为不正确的方式来做到这一点,还是因为您已经见过其他语言可以做数组,并且有一些示例可以复制,但您选择不复制?

    00:11:50.18 ]是的,您必须确定语义是什么。至少Ken和我当然来自相当沉重的C语言思维方式,因此我们花了一些时间才放弃了其中的一些想法。但是我确实想要C所没有的一件事,我想Ken和Robert会同意,那就是我们想确保我们有某种方式来做可变长度数组,或者我们现在称之为切片……而如何在C内存模型中执行此操作有些棘手。

    显然,还有许多其他语言已经完成了类似的工作,但是我们必须决定哪些子集或如何选择它们所支持的功能的行为,以使其与我们尝试构建的语言模型最匹配。仅仅从其他语言中获取功能并将它们粘合在一起,就无法获得良好的设计。相反,我们尝试为所有部分协同工作的语言建立一个一致的模型。地图和切片很困难,因为至少从Ken和我的角度来看,我们必须做的事情与通常考虑这些事情的方式大不相同。罗伯特可以为自己说话。

    是的,所以我来自完全不同的背景。我不一定会和C一起成长。我与Pascal及其继承人一起成长。在其中一个继承人中,有Modula 2,然后是Oberon,他们有一个相似的功能,即所谓的开放数组,它的大小是动态的……但是它们只能作为函数参数传递,所以说话。因此,根据要传递的数组的类型,函数中有一个开放大小的数组,一个动态大小的数组。很好,但是它没有我们想要的灵活,因此花了一些时间才能从C的各种想法(也许是从这个想法)获得到现在的状态。

    映射和切片都具有该属性,至少在基本级别上,这对于C中的任何内容都不是正确的,这是内存表示在某种程度上对用户不可见。它们具有更复杂的结构来保存数组的长度,映射的哈希桶或其他内容。在C语言中,从根本上讲,您从没有像这样的东西……这是一个挑战。后来证明这是一个挑战,因为要使切片和地图正常工作,必须将它们作为描述符的地址传递到描述符块中,并且我们一直在努力如何最好地向用户隐藏这些指针。

    有一阵子它们很明显,但是有点不舒服,所以最终我们崩溃了,把它们完全隐藏了。但是要做到这一点,我们有点不得不改变内存分配的工作方式,这就是为什么有两个分配器的原因:new和make。我对此从未感到满意;我认为没有人真的对这一切的实现感到满意……但是在实践中还可以。

    实际上,当Russ出现并决定我们可以在字面上使用地址创建运算符时,可以摆脱新的情况时,它实际上要好一些。那整理了一些东西。因此,大多数人只看到现在做;他们再也看不到新东西。这可能是针对此受众的一些特定内容,但是…

    没关系。

    …就是这样。

    我认为这是该观众的播客。您提到了一些令人惊讶的时刻,也提到了成为Go历史的历史要点的时刻。首先是伊恩(Ian)到来,让您在办公室里感到惊讶,并说:“嘿,您编写的规范-我已经为它准备了一个编译器。”还有其他时刻可以让您记住您感觉像拐点或转折点的地方吗?在那些早年?

    Russ在Ian之后加入了。他曾与Jeff Dean一起在Google实习。他做了一个代码搜索外部启动程序,作为一名实习生,这真是太了不起了……我曾在贝尔实验室与他一起工作过。他是Bell Labs声学部门其他经理之一的儿子,我认为不是计算机领域的。但是他挂在一群贝尔实验室的孩子周围,我认识他已有一段时间了。我认为他的名字至少出现在我和布莱恩·克尼根(Brian Kernighan)所写的一本书中……而且我非常努力地努力让他来。

    00:16:13.04 ]我想我实际上是在悉尼,大约在罗伯特(Robert)在那儿的时候,我们正在编写规范,对拉斯(Russ)进行视频采访,告诉他我们在做什么,以说服他来帮助我们。我们……他决定来。

    因此,我认为他大约在2008年中期出现,并加入了团队,对清理我们遗留下来的一些杂物确实起到了很大作用,并确实帮助我们将其推向了某个地方。所以他的到来是一件大事。

    那时我们只有五岁,而我们五个人一起工作了很长时间。我认为从那时起至2009年发布会之间,我们仅增加了一些帮助人员。听起来像罗伯特吗?

    是的 我认为大约在2009年,我们至少有了亚当·兰利,然后又有一两个……

    但是他只是在帮助。尽管他为我们做了很多工作,但他并不是正式加入小组。我们很幸运。

    那是对的。

    他做了很多加密工作,并帮助我们建立了第一个网站……类似的事情。

    是的是的。是的,我想我们是五​​六岁,是的。有一个女人-不幸的是,我忘记了她的名字。

    是的,珍妮·金。是。

    这就是所有预开放源代码。您是否想谈一谈2009年11月10日这一盛大的一天,当它开源时的旅程?

    我们知道,如果我们能够做到这一点,它将成为开源的…因此我们计划将其作为开源版本。但是我们希望能够做到正确或尽可能接近正确,然后再向世人展示。我们启动它大约需要两年的时间。在过去的几个月里,尽管我们并没有摆脱一切,但我们还是非常急于清理一切让我们感到尴尬而无法放开的东西。

    这些常见的问题-从公司内部发起,我们必须处理商标,专利和所有无关紧要的事情才能获得许可权。我会说,尽管谷歌在开源软件方法方面绝对是太棒了,而且从我的经验来看,与从AT&T内部发布东西相比,从谷歌内部做起来要容易得多。但是要做到这一点,我们必须决定核心库中必须包含哪些内容。亚当为我们做加密术真是太棒了,因为它启用了TLS和其他类似的功能。实际上,Go已经成为许多加密工作的主要支柱,这在很大程度上要归功于Adam。

    我们必须建立一个网站,以便人们可以看到它。我们必须制定规范,必须处理内容管理系统…我们从SVN开始,然后转移到Perforce,因为这是Google内部使用的方法。但是后来Git开始发生。我认为Go的创建早于GitHub,而不是Git本身。然后我们运行了M​​ercurial,因为那是Google的开源产品处理的……所以,我认为我们使用Mercurial已有2到3年了,一旦很明显那是未来,我们最终就改用Git。

    因此,Go实际上拥有四个内容管理系统-SVN,Perforce,Mercurial和Git。这是热爱社区的一部分-不变就是不变。

    这就引出了一个很好的问题,那就是一旦您将其发布给开源,既然您有一个社区加入并提出他们的意见并共同创造,那么这如何改变动态?

    好吧,我认为一开始反应就分为“哇,这太好了或很有趣”和“这太可怕了”。然后我就慢慢地将它带走了……

    00:20:04.05 ]我认为很多人在我们首次发布它时并不了解这一点。这看起来不像是一种有趣的语言。“为什么会这样?为什么它没有我期望的所有这些功能?”等等。语言对我们来说的重点是,我们试图使我们更轻松地构建我们在日常生活中编写的软件,并且我们认为我们并不需要所有的复杂性就能完成一件好事。的工作。

    但是一旦人们开始使用它-我认为仍然存在仇恨,但令人高兴的是看到这种心情从“这毫无价值”逐渐转变为“实际上,这还可以”到“哇,这太好了!”真正发生事情花了几年时间。

    第一个GopherCon发射后已经走了几年,我记得当时有500人左右的那个房间里的感觉,所有人都很兴奋。想到我和罗伯特和肯因为我们做了一些事情而把这个人带进一个房间,真是一种了不起的感觉。这真是一件很棒的事情。但是我们永远做不到–花费了这些时间才进入了一个社区。它不是一夜之间发生的,它是渐进的。

    是的是的。至少对我来说,这很有趣,当我想到第一个GopherCon时……我认为我们不太确定这是否真实,因为从某种意义上说,我们与该事件无关。我们没有组织它,但是显然我们是受邀的。。。当我们出现在那儿时,还不清楚要期待什么。“这会是一件大事吗?一个房间里将要坐24个人吗?”结果是有数百人和一个组织得很好的活动,这是一个很大的积极惊喜。

    真的很有趣。

    很有趣,是的。我认为在这一点上也有所帮助的是,不久前开始流行的Docker实际上将Go用于其许多软件。我想,也许是第一个GopherCon,给了我们第一个重大突破。你同意吗?我不确定。

    是的,Docker是我们的杀手级应用程序,因为它是用Go编写的,它运作良好,并且成为了现在所谓的云计算的核心……我们过去仅将其称为系统编程或服务器。而且其中一项关键技术是用Go语言编写的,这一事实证明了该语言对于许多人来说是合理的。我认为这实际上是一门非常好的语言。这是我们在将语言组合在一起时所考虑的事情,尽管我们自己并未这样做。

    后来,Kubernetes又出现了,这一次来自Google。但是用您的语言编写出色的软件是使一种语言取得成功的真正重要组成部分。如果没有编写任何语言,那么语言的好坏并不重要。

    你们是否知道Docker团队在开始时就在Go中编写它?您是在积极地与他们互动,还是在某种程度上令人震惊?

    不,我们没有参与。后来我们发现了。我遇到了所罗门;他是在Docker上工作的那个人……我想他是团队的负责人,不过我不确定。所罗门·海克斯(Solomon Hykes)。他在某个时候来到了位于旧金山的Google办公室,我们聊天了,但这是我第一次见到他,也是我第一次真正与任何人交谈。但这在当时已经很确定了。

    在一次会议后,我确​​实在YouTube视频上看到了一个演示,并且可以说这是我眼前的未来。这是一个很大的问题。Docker是一项非常不错的技术。Google在操作系统级别上为内部系统工作做了一些工作,并在其上面放置了一个非常漂亮的用户界面并进行了打包,以使其实际上可用于日常工作,我认为是一个非常不错的项目。这变成了一个不错的大型项目,并启用了Kubernetes以及我们今天用来运行大型系统的所有其他云级别的东西。

    打破

    00:24:27.28 ]

    因此,在经历了这一重大突破之后,有哪些(如果您还记得的话)成长中的烦恼是什么?现在Go开始被采用,现在是云计算的语言了吗?您认为您会想到什么成长的烦恼吗?或者,换句话说,考虑到那些不断增长的痛苦,您是否希望做其他任何事情?

    嗯,从来没有什么完美的……我想更改的语言有很多东西,但是也许我不应该在这里深入探讨。我确实认为该团队并未真正做好与开源社区进行交互的准备,这意味着什么。Ian是我们中唯一在开放源代码世界中度过了很多时间的人,他所做的不只是他在社区中所占的份额。

    我们花了很长时间才了解成为开源社区一部分的意义,拥有一个基本上由公司支付的项目,但是有很多开源贡献者……实际上,我们有很多很棒的开源发展发生得很早。Windows的移植工作完全由外部贡献者完成,这真是太好了……社区的意见至关重要。

    我认为有时候人们会认为Google控制得太多,这是他们的观点,但是我不同意。我认为他们低估了团队听取开源社区所说的话,阅读所有问题,很好地处理所有问题的程度……有时不是很好,但是后来就解决了。

    00:28:02.06 ]当有成千上万的人时,这是一个非常具有挑战性的事情,现在,据信世界上有数百万的Go程序员。他们都对这件事以及如何听取意见,但也要确保您正确把握项目的灵魂-我认为对此没有任何简单的答案。我认为很多人认为这很琐碎,您只是接受了每个人想要的东西……但是那样的话您就不会拥有Go,而您会拥有其他东西。这确实很棘手,这是非常困难的平衡行为。

    我怀疑部分人有这种感觉的部分原因是因为,就像我自己一样,我在一个网站上工作,可以重构整个思维;或者我在一个可以发布新的主要版本的库上工作。是的,我可能第一个错了,但改变起来并不难……而你们正在处理的东西在这种意义上很难改变。

    好吧,我们很难更改。对于Go 1,我们刻意写下我们保证不做任何更改。这对于语言的成功至关重要,因为它使企业能够相信我们正在做的事情以及依赖我们的事情不会破坏他们的工作……而这使得进行更改变得更加困难。我认为很多人不欣赏我们对合同的热情。尽管这是一个已有十年历史的项目,但我们并未违反人们的计划。这是一个令人难以置信的负担,但对于使我们到达现在的位置至关重要。

    那就对了。一旦有了1.0,几乎就是公司开始使用它的时候。以前,这就像“很有趣,很酷……”,那也是我们停止进行任何重大更改的时候。

    我们并没有多说一件事-即使在2009年发布该语言之后,我们仍然对该语言进行了相当多的更改。例如,如果我没记错的话,分号在我们的初始版本中仍然存在。我们可以进行很多更改,然后在1.0之后停止。

    在1.0之后,您将无法进行这些更改。他们有时仍在挑战以做出那些自以为是的改变吗?我可以举一个例子,就是有些人不喜欢这样的事实,当您有未使用的变量时,它会给您带来编译时错误。我怀疑如果您以后要添加,那就是这种类型,因为很难,因为有人会想:“您为什么这样做?您正在破坏我的代码。”因此很明显,这将破坏1.0的承诺。但是在此之前,您获得了开源社区的支持,还是相对容易做到?

    我认为我们没有太多反馈。除了可能的bug,我认为我们没有-首先,我们没有适当的流程来处理功能请求或类似的事情。那时我们还没有真正看到类似的事情。当然,在1.0之后,我们不能再进行此类更改,只是因为它会破坏兼容性,而这是我们不想做的事情。我们仍然不这样做。

    Go的某些功能对人们的成功至关重要,这是人们所不喜欢的,我们对此非常直言不讳。我认为您提到的一个,其中一个是未使用变量的编译错误。这很烦人-您忘记删除未使用的变量,程序就会编译。但是对于我们来说,这只是我们要讲的故事的一部分,这就是使一种语言能够尽可能保证高质量的代码,即使我们不能阻止您编写不良代码……但是我们可以确保事情不会拖延,这会使您的构建速度变慢,或者代码难以维护。

    00:31:41.28 ]我认为真正使人发狂的一个原因是您不允许导入不使用的库。这对我们至关重要,因为我们花了很多时间来进行大量二进制文件的缓慢构建,确保您程序的依赖项完全是您所需要的,而不再是其他依赖项。这对我们至关重要,但对于很多人来说,每次您进行编辑并删除打印语句或类似内容时,编译器都会说“您没有使用此库。我不会再建立你了。”

    然后,布拉德(Brad)编写goimports了一个名为的东西,这是gofmt为您管理进口商品的一种变体,几乎消除了该抱怨。通常,自动化可以消除很多烦恼。

    导入的要点–当然,编译器可以很容易地弄清楚是否使用了导入,但要点是您实际上看到自己在依赖其他内容,并且在视觉上得到了提醒现在,您要添加一个新的依赖项。

    这是一个事后偏见的问题,但是您是否预见了10-12年后的软件重用情况?

    没有。

    所以这只是一种幸运的猜测,还是直觉?

    嗯,这并不是软件本身的重用,而是经验,尤其是在Google上,我们拥有一个庞大的环境,可以在您的程序中使用成千上万个潜在的库,而且我们已经看到了一些清理工作,有时,二进制文件的大小减少了40%或50%,因为从树中修剪了真正未使用的依赖项。因此,我们知道依赖性控制是保持构建整洁的重要组成部分,而该语言实际上可以为您提供帮助。在少数地方,一种语言可以通过强制执行某些规则来使软件变得更好,这很容易。这很容易,值得。

    但是人们对此颇有好感,因为编译器会因为看起来像是天真的错误而大喊大叫……但是我们希望编译器只接受干净的程序。就像我说的那样,社区–我们收到很多关于它的邮件,但都在抱怨,但是Brad只是通过制作一个可以完全解决该问题的工具来解决了这一问题,这很棒。

    这是诸如此类之类的工具背后的动机gofmt,还是您只是试图从根本上强迫人们使用符合某些标准的代码?因为我知道您看到的所有其他语言,所以每个人对于Prettier,JSON或他们所做的任何事情都有不同的设置-他们有一些随机的“这就是我们使用的”设置,因此无论您走到哪里,都变化。

    gofmt作为可读性审核者,我的沮丧感有所增加。大多数公司,当然还有Google,都有一个我们检查彼此代码的过程,以便对所有签入的代码进行同行评审……而且,大多数评审遵循样式指南。而且,如果您在该样式指南中查找了诸如C或C ++之类的语言,则许多样式指南中都充满了“您应在此处缩进很多,并且需要在其中留有空白”等等。与工程或您编写的代码实际上没有或没有多大关系的事情,只会花费很多时间。所以我觉得这是我们应该完全自动化的事情。数以千计的工程师浪费了很多时间,基本上是告诉别人“您是否需要在此处放置空白”,或者遵循某人编写的某些样式指南。

    格式化程序是在过去编写的,这不是第一次,但是我建议我们应该这样做,而且我想这样做。而且Rob基本上说:“您知道,表明它可以完成。”花了一段时间,毫无疑问;它花了几年时间才到达现在的位置,而且显然还不完美,但是人们已经爱上了它gofmt,尽管他们gofmt有时讨厌自己的风格。

    00:35:57.00 ]我认为gofmt是在一两天内就开始了。我们知道我们想执行它。

    是的,是的。

    并完全感谢Robert的成功,因为这是一个真正的工程挑战。但是我相信Go是通过这样的外部工具强制进行格式设置的第一语言。还有一些语言在语法上的不同。但是Go是第一个说“您在程序上运行此工具,然后我们强制采用这种格式的工具”。它影响了社区的其他成员。其他语言得到了支持。现在有一种Java格式化程序被广泛使用,Rust有一个,C ++通过Clang有一个,而且我认为越来越多的人正在理解它的价值。

    从我的角度来看,该项目中发生的一件非常有趣的事情gofmt是很棒的,最终被所有人采用,但是它提供了一种我们没有想到的工具。因为事实证明,如果有的话,gofmt主程序基本上就是一个环绕着进行打印的库的主程序……并且不久之后,我们意识到,如果您有一个可以格式化代码的库,则可以编写可在软件并自动进行重构,然后生成完全有效的,格式整齐的输出……这使许多动态编辑工具可以直接在代码上运行,但仍可以生成可签入的代码。我们有一些。

    Go 1.0的启动-库中有大量更改,语言的一些细节也有所变化,Russ编写了一个程序,称为gofix,其中包含这些小插件模块,这些小插件模块实现了语言的更新或库使用的更新……但是有关该过程的令人惊讶的事情是,我们大约每周都会发布一个很小的版本,并且通常带有一个Gofix模块,如果您是该语言的用户,则可以更新Go安装,然后在所有代码上运行Gofix,它将完全自动将其更新。因此,我们带动了整个社区。我们不是通过拥有功能或“如果……那样”来解决兼容性问题,而是创建了一个工具,该工具可以使每个人随身携带其软件,并随时了解正在发生的变化。而这是通过使其成为可能的gofmt,但是我不相信我们直到真正发生时才意识到这一点。

    是的,我认为这是– Russ开始的。

    是的 我们对进行了难以置信gofix的大规模重构,尤其是在Google树中。这是一个了不起的发现,我认为完全是出乎意料的。

    您谈论gofmt它及其意想不到的后果……告诉我您如何看待Go对开源世界的影响。

    我绝对认为,今天-也许不是专门针对开放源代码的世界,而是说​​一些较新的语言...如果您现在要推出一种新的语言或系统,则可能想发送某种格式程序。它几乎已成为标准要求。我认为一切统一格式的事实可能会在每个人都希望这样做的意义上影响开源世界,因为它实际上具有一些积极的副作用,例如当您与变更合并时,您可以减少人为变更的数量只是由于格式上的差异……所以这里有一些协同效应。

    另外,所有代码看起来都一样,听起来很奇怪,但是–没有两个C程序看起来相似,但是每个Go程序看起来都一样。我认为这增加了您使用语言,与其他人一起工作,理解它的难易程度……太好了。

    00:39:58.22 ]我们要做的另一件事是我们–语言不是第一语言,但在严格地作为UTF8源代码方面,它是最能说明问题的。我们刚刚对所有这些可笑的其他编码说了再见。我不会因为改变UTF8在世界上的重要性而归功于Go,但我认为Go之后出现的几乎所有语言都对UTF8输入具有相同的规则。

    我认为这对我们也很重要-希望我们产生更大影响的是您首先编写规范的想法。我认为许多其他语言的后续工作可能会从中受益。我知道Rust仅在现在才获得正式规格。就我所知,这本书正在进行中……而且我发现很奇怪,您将在不确切知道要实现的语言并将其编写下来的情况下实现编译器。

    拥有规范的另一件事是,它使开箱即用的替代实现成为可能……现在有很多Go编译器。有一些使用Go to Javascript,有一个在GCC / Clang套件中,有一个LLVM Go,有一个我们自己为Go项目运行的原始Go编译器,所有这些都是基于规范的……而如果您没有规范,而您所拥有的只是编译器,限制了您可以学习的知识,以了解该语言的正确之处,该语言的错误之处,其他技术以及类似的东西。因此,我认为制定规范并没有得到应有的重视,但我希望如此。

    我认为这里的区别在于,使用Go语言时,我们并没有真正尝试进行语言研究。我们试图根据已久的实际语言设计和技术提出一个更简单的工具,然后以更新,更现代和更好的方式将其打包。

    许多更新的语言-当然,在我看来,Rust实际上正在进行语言研究,所以……有很多未知数。

    是的,他们正在尝试一种非常不同且非常聪明的方法,我希望它能够成功……但是,是的,他们正在尝试解决一个与我们试图解决的问题截然不同的问题。我们还想到了什么,认为影响发生了?

    我认为我们对兼容性的立场对社区来说也意义重大。我们之前已经提到过,但是我认为其他人可以通过认真思考他们如何以我们所拥有的精度来实现向前和向后兼容而受益……因为这对我们和我们的社区都产生了巨大的影响。毫无疑问,这会使某些事情变得更加困难。如果您有一个好主意,则不能仅仅实施它。如果发现错误,则无法修复...但是社区和所有软件的稳定性对于Go生态系统的增长确实非常重要。

    在过去的十年中,您对软件行业和编程语言的发展感到惊讶吗?

    我认为所有人都对开源如何成为主流感到惊讶。我认为GitHub在2007-2008年左右发布时,大约在Go发生的同时,也发生了GitHub。…在GitHub之前,对于很多人来说,开源是非常利基的市场。但是现在企业软件系统几乎都使用了一些开源组件,我认为行业改变这种工作方式已经是一个突然的改变。从网上获取代码不只是开源。整个过程,包括如何管理依赖项,如何进行更新,如何在分布式环境中构建,使用Web上的Git和代码审查工具以及所有类似的东西。所有这一切都是新的,我认为开源社区为现代软件开发做出了巨大贡献……

    00:44:10.07]但这不只是开源社区了;整个软件领域现在都在使用这些工具……我认为这完全是出乎意料和令人惊讶的,但是它也带来了一些非常棘手的问题,例如依赖管理以及如何保持依赖的安全性和最新性。现在,典型的Node安装将有大约一千个依赖关系,这简直太疯狂了……而且我认为您无法自信地说可以信任不拥有的一千个依赖关系。您怎么知道代码是好的,安全的,健壮的,受保护的,正确的更新时间,错误的更新时间,错误已修复-所有这些问题都非常棘手。Go也已经拥有了。因为它是其中的一部分,所以它从开源生态系统中获取依赖关系。依赖树的规模对于Go来说并不像在其他一些世界中那么大,但是仍然很大。例如,它比C ++程序通常要大得多……而您如何知道自己拥有的是值得信赖的呢?

    Go团队在尝试提高从网上抓取代码的安全性和可靠性方面做了很多工作,但是……我认为,这仍然是一个令所有人感到惊讶的问题。

    让我感到惊讶的一件事是Go问世后不久出现了多少种新语言。因为在2007年左右,语言世界似乎有点停滞不前。有C ++,有Java,Javascript,但除此之外没有太多。

    蟒蛇。

    当然是Python。是的,用途广泛……然后Go出现后不久,到处都有许多不同的语言出现,我认为这很有趣。我认为少即是多的想法开始引起更多人的共鸣。我认为这是一个积极的发展。

    不是所有人。

    不是所有人,是的。

    打破

    00:46:17.18 ]

    我认为编程语言生存的时间越长,就越需要克服复杂性或功能蠕变,对吗?

    因此,那些存在时间更长的应用程序必须尝试–简单化是还原性的,但是为了使事情变得更简单,他们必须编写包装器和超级包装器,并且它们是累加器,这有点矛盾……所以我认为这也是我们在编程语言发展史上所处位置的一种功能。

    我认为这是一个战略问题。您可以选择其他方式。还有其他语言。C ++就是其中之一,Perl可能包含了复杂性。我赞扬Bjarne Stroustrup(C ++的作者),因为他给了用户他们想要的一切。他们要求更多,并且他给了他们更多,结果他最终建立了一种语言,这种语言一直是而且仍然是全球软件开发的关键部分。Google的核心仍然主要是C ++,我相信许多其他公司也是如此。

    00:48:13.07 ]那是我们采取的完全相反的策略,那就是锁定它而不是改变它。为了锁定它,您必须相信自己的愿景是有道理的,这是正确的要做的事。我并不是说这些方法中的任何一种都是更好的。它们只是完全不同的策略,两者都可以起作用。这是您必须在系统中的某个时候做出的决定,您想采用哪种方式。

    我发现令人惊讶的是,C ++在2009年变得更加复杂,而且可能仍然如此。而且,您是对的,如果您想保持向后兼容,即使您在此处和此处添加了一些小东西,随着时间的流逝,语言当然也会不断发展。我个人希望继续开发模块,通过说如果您使用的是1.15版或类似的版本,您将不会有一些我们认为已经过时的功能,或者也许是不合适的或设计得不好,相反,您可能会得到其他东西。因此,至少这是我的希望,也许我们可以遏制这种增长并保持增长的势头……但我们会看到的。

    拥有工具也有帮助,因为与gofix- 一样,您可以想象有一个新的工具可以gofix帮助我们在前进的过程中清理外界的代码库……这是Robert所做的另一件事(与gofmt有关);在标准库中使用该语言的解析器和词法分析器可以非常轻松地编写工具。

    来自开源社区的早期事情之一就是对IDE的要求。“ Go IDE在哪里?我想要的Go专用编辑器在哪里?”这从未发生过。我们没有创建它。有很多……GoLand现在是特定于Go的,但实际上只是IntelliJ的一个版本。相反,我们拥有的是一个非常好的库,用于分析Go程序并对其进行编辑,并且具有熟练的程序员(无论如何都不是专家)基于该库编写工具的能力。因此,我们没有创建用于Go的IDE,而是创建了一个库,该库使编写IDE的插件变得容易。因此发生的事情是所有IDE现在都很好地支持Go,但是我们从未编写过Go IDE。这是另一个战略问题。我认为这不是故意的。我认为这是又一次事故。我们有点想要一个Go IDE,但是从来没有觉得我们是合适的人。但是,取而代之的是,它变得不必要了,因为Go与其自身工具的集成非常有效。

    那是另一回事–你提到卡门,我们做了什么;我不为此而感到自豪,我真的认为Go并没有全部开始,但这是一个生态系统的真正好例子,而不仅仅是一种语言……它带有自己的构建工具,自己非常强大的库。您可以直接用大约十行代码编写可用于生产环境的Web服务器。与依赖项管理的集成不同于人们今天想要的,但我认为它从很早就开始了。现在,模块的内容正在更直接地解决。但是,对于像这样的编译语言来说,随语言一起提供语言工具是一个不寻常的步骤。我认为Rust的货运系统表明这确实是可行的方式。那是一个变化。

    好吧,我们还剩下大约十分钟的时间,我想谈谈Go的持久品质。我们将迎来十年并庆祝十年……下一个十年呢?您希望Go在第二个十年或历史史上将走到哪里?

    00:52:07.15 ]它已经超出了我想象的范围,所以我不知道我对它前进的方向的想法……我永远也梦想着它不会像现在那样脱颖而出,成为尽可能大和主流。它不是世界上排名第一的语言,它永远不会-并非意味着-坦白说,它的成功一直令我们感到震惊。

    是的,我完全同意。我认为我们不可能预见到这一点。我认为只有时间才能说明十年后的发展趋势。

    您认为它将经受住时间的考验吗?

    我认为这取决于那段时间。我们已经有十年了-从一开始的确是十二年-而且看起来还不错。但是事情可以改变。我认为我们对社区的方法有了很大改进。我们的社区正在成长,这个社区感觉像我们是一个热情的社区。我认为其中很多可以追溯到安德鲁·格朗德(Andrew Gerrand)的所有工作,他在这方面做了大量的社区工作,并制定了社区行为准则等。我认为这是一个重要方面。然后,当然还有语言,库和其他东西。

    Russ在模块方面的工作是巨大的进步。这就是我们最初以某种方式错过的东西;我们没有真正很好地研究供应商和依赖性问题。我认为这是业界希望看到的东西,他们对此非常满意。

    我认为,这是我们在过去几年中朝着正确正确方向迈出的一些重大步骤。而且我认为房间里有个大大象,这是通用功能……而且我认为我们正在放大某些内容,但是我不说最后一句话,也不知道我们是否要去那里, 当然。

    无论Go作为语言和生态系统的遗产如何,我认为它所产生的影响将经受住时间的考验。我认为由于罗伯特(Robert)的缘故,gofmt现在几乎已经接受了布局代码的大部分工作应该由工具而不是人来完成。我认为,专注于制定正确的规范并考虑确保具有正确的功能非常重要。强迫UTF成为语言规范,做很多我们之前提到的事情-它们会起作用。作为标准流程的一部分,我们进行代码审阅的方式-不仅是拉取请求,而且实际上我们还使用了一个不错的工具套件进行了完整的审阅……这种事情。

    我们已经与其他项目进行了交谈。我们想知道我们的工作方式以及我们如何接受社区贡献,有时他们会惊讶于我们首先查看它们,而不是先接受然后再清理。这只是一种态度。我们要确保进入系统的所有内容均达到最高质量。这种方法并不是普遍喜欢的,但效果很好,而且我认为许多其他项目也已经学会了考虑项目的运行状况,而不仅仅是功能集和获得的用户添加它们。

    因此,我们的生态系统中的某些方面不一定具有开创性,但无论Go发生什么,都会对未来系统的构建方式产生一定的影响。

    00:55:49.16 ] Go社区仍在不断发展,谁知道它会变得越来越大。正如我所说,我认为这不会成为有史以来的第一语言,甚至不会接近它。教育还没有建立起滩头堡的一个地方。我想看看。我认为,除非在大学里教授过,否则它永远不会真正成为主流语言。而且,这几乎还没有发生。有一点点,但还不够。既然Python几乎已经成为除系统软件以外的所有语言的事实语言,我认为Python是您应该谈论的未来语言。

    嗯……真可惜,因为我选了计算机科学,但我真的不喜欢它。我告诉大家这个故事。我只是希望自己拥有Go语言,因为我确实觉得Go语言是一种我们可以完全重新考虑如何教授计算机科学的方法。

    好吧,现在大多数科学软件开发都是用Python完成的。没关系,我对此没有任何问题。但是正因为如此,并且由于许多通用软件教育都是使用Python进行的,因此很难将如此清晰明了且与机器接近的语言纳入标准课程。我没有抱怨,这就是事实。

    我很想看到Go用于教学。不一定是入门语言,而是大学课程的一部分。但是到目前为止,它实际上仅在一些专业课程中才发生。

    我认为大学的问题之一是,他们几乎具有这样的授权:要教学生行业需要什么,而这并不是我上学时要做的。当我上学时(我在谈论计算机科学),我们了解了技术和不同种类的语言以及不同的做事方式,这些不一定与当时的行业紧密相关。可能是COBOL或C。因此只要不改变,大学将很难真正拓宽视野并使用其他语言。

    由于机器学习,Python现在特别受关注。Python使您可以轻松地连接本质上为C的库,它只是在顶层。

    好吧,Jupyter Notebooks绝对是一件了不起的事,我希望我在学生时代就拥有过。

    辛苦了!那将改变生活。好吧,乔恩,您对罗伯或罗伯特还有其他问题吗?

    我想我想问的是你们前面提到的,当您开源时,您还没有为此做好充分的准备。参与其中就像是一个学习阶段。我想至少对于我来说,我知道我发布的第一个开源项目,我做的最大问题可能与你们所做的相反,我基本上把一切都扔给了我,因为我很兴奋人们足够关心要做某事,而只是想把它全部拿走……大概三个月后,我正在研究它并试图对其进行维护,我感到“这真的很难维护”,因为我犯了一个错误,那就是仅仅使用所有功能。我所能做的一切 你们的心态相反。

    好吧,我认为行为准则业务虽然对某些人很有争议,但却是建立社区的重要组成部分。我认为人们需要了解这是一个相互尊重的社区,不受欢迎的巨魔……尤其是在今天,大声说出来似乎更为重要,但我认为这是建立健康社区的重要组成部分。

    00:59:53.14 ]从技术角度来看-是的,您必须密切注意奖金。如果让不好的功能或太多的功能不受控制地进入,最终将导致很难维护的软件。但是,当社区正在推动您不满意的事情并确保确定时,需要大量的工作来使社区参与,并且您将把人们赶走。有人会向您发送请求请求,然后您说“您知道什么,我不想要这个”,然后您会解释原因,做好解释,但是他们可能仍然觉得您错了,并且被冒犯并带他们的玩具回家。因此,您必须准备好尽可能愉快的同时说不,这将是非常困难的。

    是的,我认为这就是重点。您基本上想要坚定但礼貌,并且想要确保人们觉得您在听他们的话,基本上验证了他们在说什么,但这并不意味着您必须接受每一个建议实现其他人想要的。我认为这里有一点。

    但这就是说,会有很多东西进来,这很棒,但是在您接受它之前,需要先对其进行精炼和抛光。而且,如果您有礼貌地交往,并解释您想改变的地方,那么如果他们的东西落空,您将赢得一个盟友,这将使另一个愿意帮助的人在系统上变得更好。

    顺便说一句,如果您可以说服某人为什么某些功能请求可能不是一个好主意,并且您可以说服他们,那么您也有一个盟友,因为他们意识到“哦,好吧,这些人是真的在考虑这些东西。”

    您想为新一代的地鼠提供任何建议吗,在我们关闭之前,你们两个之间还有什么遗言吗?

    好好享受!我们早期使用的一个词-我们想再次使编程变得有趣...因为它已经成为了-当然对于我正在研究的某些东西,可能还有Robert-只是一个口号。45分钟的构建和单行编辑会导致整个系统发生重大变化。我们想要的东西要轻一些,我想确保我们记得编程是一件有趣的事情。

    现在在云开发环境中工作-有很多活动部件。那里又变得复杂了。确保您专注于正确的更改,以及正确的处理方式,以使操作灵活,适应性强并且有趣。多不一定总是最好的。有时候,精打细算可能是前进的更好方法。

    是的,我认为保持开放的态度并在框外进行思考很重要。仅仅因为某件事已经以某种方式完成了五年,并不意味着这是正确的方式。我想回想一下我以前讲过的关于语言和教育的内容……今天,大多数人在参加正规的计算机科学课程时可能已经看到一种或两种语言。Java可能是其中之一,Python可能是其中之一,但是它们属于同一类语言。

    过去很少有人见过真正不同的语言,例如Lisp,Scheme或Smalltalk,那里的事物(或功能语言)与主流语言完全不同。这些语言为您提供了可能会改变您的观点的不同想法和思考方式。但最重要的是,我想我们要确保尽可能降低复杂性。我们确实必须使它尽可能简单……听起来很容易。每个人对简单事物都有不同的想法,但这确实很难。您想在所有情况下都尽可能简单,因为它会在某些时候咬住您。

    听起来像是新的KISS首字母缩略词。这很棒。感谢Robert和Rob,今天与我们在一起,庆祝GoTime的第100集。我们真的感到很荣幸,很高兴有您。

    感谢您的光临。

    这是卡门。直到下次,谢谢大家。

    翻译自:

    https://changelog.com/gotime/100

    展开全文
  •  高级编程语言的发展历程(三)FORTRAN 语言是怎么来的  高级编程语言的发展历程(四)LISP 和 AI 的青梅竹马 A  高级编程语言的发展历程(五)LISP 和 AI 的青梅竹马 B  高级编程语言的发展历程(六)SCHEME...
    目录 
    

      高级编程语言的发展历程(一)创始纪
      高级编程语言的发展历程(二)虚拟机的前世今生
      高级编程语言的发展历程(三)FORTRAN 语言是怎么来的
      高级编程语言的发展历程(四)LISP 和 AI 的青梅竹马 A
      高级编程语言的发展历程(五)LISP 和 AI 的青梅竹马 B
      高级编程语言的发展历程(六)SCHEME 语言是怎么来的
      高级编程语言的发展历程(七) LISP 语言前传

      原文标题:高级语言是怎么来的

      高级编程语言的发展历程(一) 创始纪

      2009-5-13 原文链接

      终于放暑假了,有心情来八卦了。我主要想八卦一下高级语言的设计思想和各种范式的来龙去脉,也就是回答这个问题:编程语言为什么会发生成现在这个样子哩?这里面的奥妙又在哪里哩? 我尝试着把这个系列的八卦写下去,包括虚拟机的设计、线程的设计、栈和寄存器两大流派的来龙去脉等等。

      高级编程语言的创始纪上写道:“初,世间无语言,仅电路与连线。及大牛出,天地开,始有 FORTRAN, LISP。ALGOL 随之,乃有万种语。” 我们都知道,LISP 是基于递归函数的,FORTRAN 是做科学计算的。现在的 C 等等,都比较像 FORTRAN 而不像 LISP。可是很少有人知道,最初,FORTRAN 是不支持函数递归调用的,而LISP是一生下来就支持的,所有高级语言里面的递归调用,都是逐渐从 LISP 那里学来的。这段尘封的历史非常有趣,值得八卦一番。

      一般人学编程,除了写 Hello World 之外,人生写的第二个程序,不是阶乘就是菲波拉契数列,要不就是汉洛塔。而这几个程序,基本上都是因为函数的递归调用才显得简单漂亮。没有递归的日子里, 人民非常想念您。可是,第一版的 FORTRAN 就居然不支持递归。 细心的读者要问了,不支持递归的语言能图灵完全么?当然可以,图灵机就是没递归的典型的例子。但是没递归调用的程序会很难写,尤其像汉诺塔这种。那 么,FORTRAN 他怎么就悍然不支持递归呢,让我们回到 1960 年。

      话说当年,IBM 是计算机行业的领军者。那时候的计算机,都是比柜子还大的大家伙,至于计算能力嘛,却比你的手机还弱。那时候计算机所做的最多的事情,不是发邮件打游戏, 而是作计算。作计算嘛,自然需要一种和数学语言比较接近的编程语言。于是,1960年,IBM 就捣鼓出了 FORTRAN,用行话说,就是公式翻译系统。这 个公式翻译系统,就成了世界上第一个编程语言。这个编程语言能做数学计算,能作条件判断,能 GOTO。用现在的眼光看,这个语言能够模拟图灵机上的一切操作,所以是图灵完全的。学过数值计算的同学都知道,科学计算无非就是一大堆数学计算按照步骤进行而已。所以,一些控制判断语句,数学公式加上一个数组,基本上就能完成所有的科学计算了。IBM 觉得这个语言够用了,就发布了 FORTRAN 语言规范,并且在自家的大型机上实现了这个语言。

      在实现这个语言的时候,IBM 的工程师要写一个 FORTRAN 编译器 (请注意那时候的大型机没有操作系统)。那时候的编译器都是用机器语言或者很低级的汇编语言写成的,所以编译器要越简单越好。这些工程师觉得,弄一个让用户运行时动态开辟内存的机制太麻烦了,所以干脆,强迫用户在写程序的时候,就要定好数组的大小,变量的类型和数目。这个要求并不过分,因为在科学计算中, 数组的维度,用到的变量等,在计算之前,就是可以知道大小的。用现在的话说,就是不能动态开辟内存空间,也就相当于没有 malloc 的 C,或者没有 new 的 C++。这样的好处是,一个程序要多少内存,编译的时候就知道的一清二楚了。这个主意看上去很聪明,不过 IBM 的工程师比你想得更加聪明,他们想,既然一个程序或者子程序要多少内存在编译的时候都知道了,我们干脆就静态的把每个子程序在内存中的位置,子程序中参数,返回值和局部变量放的位置,大小都定好,不久更加整齐高效么。是的,我们都知道,在没有操作系统管理的情况下,程序的内存策略越简单越好,如果内存放的整整齐齐的,计算机的管理员就能够很好的管理机器的内存,这样也是一件非常好的事情。(再次强调,当年还没有操作系统呢,操作系统要等到1964年发布 的 IBM 360 才有,具体开发一个操作系统之难度可参考《人月神话》)。

      可是,聪明的读者一下子就看出来了,这样静态的搞内存分配,就递不成归不了。为啥呢?试想,我有个 Fib 函数,用来计算第 N 个菲波拉契数。这个函数输入一个整数,返回一个整数,FORTRAN 编译器帮我把这个函数给静态分配了。好,我运行 Fib(5) 起来,FORTRAN 帮我把 5 存在某个专门给输入参数的位置。我在 Fib(5) 里面递归的调用了Fib(4),FORTRAN 一看,哈,不还是 Fib 么,参数是 4,我存。这一存,新的参数4,就把原来的 5 给覆盖掉了,新的返回值,也把原来的返回值给覆盖掉了。大事不好了,这么一搞,新的调用的状态居然覆盖了老的调用,这下,就没法返回原来的 Fib(5) 了,这样一搞,怎么递归啊?

      IBM 这些写编译器的老前辈们,不是不知道这个问题,而是压根就鄙视提出这个问题的人:你丫科学计算递归什么呀,通通给我展开成循环,展不开是你数学没学好,想用递归,你就不要用 FORTRAN 了。那时候 IBM 乃是老大,只有他们家才生产大型机,老大发话,下面的消费者只能听他的。

      既然软件不支持,硬件也就可以偷工减料嘛,所以,硬件上,就压根没有任何栈支持。我们都知道,计算机发展史上,软件和硬件是相互作用的。我们现在也很难猜测,是 IBM 的软件工程师因为 IBM 的硬件工程师没有在硬件上设计出堆栈,所以没有能在 FORTRAN 里面设计出递归调用呢,还是 IBM 的硬件工程师觉得既然软件没要求,我就不设计了呢?不管怎么样,我们看到的是,1960 年前,所有的机器的硬件都没有直接支持栈的机制。熟悉 CPU 的都知道,现代 CPU 里面,都有两个至关重要的地址寄存器,一个叫做 PC(Program Counter), 用来标记下一条要执行的指令的位置,还有一个就是栈顶指针 SP(Stack Pointer)。如果没有后者,程序之间的调用就会非常麻烦,因为需要程序员手工维护一个栈,才能保证程序之间调用最后还能正确的返回。而当年,因为 FORTRAN 压根就不支持递归,所以支持 FORTRAN 的硬件,就省去了栈指针了。如果一个程序员想要递归调用,唯一的实现方法,就是让程序员借用一个通用寄存器作为栈指针,自己硬写一个栈,而且不能用 FORTRAN。

      因为 FORTRAN 不支持递归调用,按照自然规律,自然会有支持递归的语言在同时代出现。于是,很快的,LISP 和 ALGOL 这两个新语言就出道了。我们只说 LISP,它的创始人 John McCarchy 是 MIT 教授,也是人工智能之父,是学院派人物。他喜欢阿隆佐·邱奇(Alonzo Church)的那一套 Lambda 演算,而非图灵的机械构造。所以,LISP 从一开始,就支持递归的调用,因为递归就是 lambda 演算的灵魂. 但是有两大问题摆在 McCarchy 面前。一是他的 LISP 理论模型找不到一个可以跑的机器,二是他的 LISP 模型中有一个叫做 eval 的指令,可以把一个字符串当成指令在运行时求值,而这个,当时还没有人解决过。按照 Paul Graham 大叔在他的《黑客与画家》 里面的说法,McCarchy 甚至压根就不想实现这个 eval 指令,因为当 IBM 的一个叫 Steve Russell 的工程师宣称要实现 eval 的时候,McCarthy 还连连摇手说理论是理论,实际是实际,我不指望这个能被实现。可是,Russell 居然就把这两个问题一并给解决了(这哥们也是电子游戏创始人,史上第一个电子游戏就是他写的,叫 Space War)。他的方法,说来也简单,就是写了一个解释器,让 LISP 在这个解释器里面跑。这个创举,让传统上编译 -> 运行 的高级语言流程,变成了编写 -> 解释执行的流程,也就是著名的 REPL(read–eval–print loop) 流程。他做的事情,相当于在IBM 的机器上用机器码写了一个通用图灵机,用来解释所有的 LISP 指令。这个创举,就让 LISP 从理论走到了实践。

      因为有了运行时的概念,LISP 想怎么递归,就可以怎么递归,只要运行时支持一个软件实现的栈就可以了。上面我也说了,也就是写解释器的人麻烦一点而已,写 LISP 程序的人完全就可以不管下层怎么管理栈的了。同时,有了解释器,也解放了原来动态分配空间的麻烦,因为现在所有的空间分配都可以由解释器管理了,所以,运行时环境允许你动态的分配空间。对空间分配的动态支持,随之就带来了一项新技术:垃圾收集器。这个技术出现在 LISP 里面不是偶然的,是解释器的自然要求和归宿。在 FORTRAN 上本来被绕过的问题,就在 LISP 里面用全新的方法被解决了。LISP 的划时代意义和解释器技术,使得伴随的很多技术,比如抽象语法树,动态数据结构,垃圾收集,字节码等等,都很早的出现在了 LISP 中,加上 LISP 本身规则很少,使用起来非常灵活。所以,每当有一项新技术出现,特别是和解释器和运行时相关的一项新技术出现,我们就会听到有人说, “这玩意儿 LISP 里早就有了”,这话,是有一定道理的。

      除了上面的软件模拟之外,MIT 还有一派在作硬件模拟,这一派,以后发展成了灿烂一时的 LISP machine,为日后所有虚拟机理论铺开了一条新路。这一派在70、80年代迅速崛起,然后随着 PC 的兴起又迅速的陨落,让人唏嘘不已.

    最后附送一个八卦:1960 年的时候,高级语言编程领域也发生了一件大事,即 ALGOL 60 的提出。ALGOL 是划时代的标准,我们今天用的 C/Java 全是 ALGOL 家族的。ALGOL 注意到了 FORTRAN 不支持递归的问题,于是从一开始,就订立标准支持递归。但是,处理递归需要很小心的安排每个函数每次调用的地址和所谓的活动窗口(Active Frame),而并不是每个编译器都是牛人写的,所以在处理递归这样一个新事物上,难免会出点小问题和小 BUG。这时候,搞笑的高爷爷(Knuth)出场了,他提出了一个测试,叫做“是男人就得负67”。(The man or boy test). 恕我功底不深,不能给各位读者把这个男人测试的关窍讲清楚,但是,我知道,这个测试,乃是看 ALGOL 60 编译器有没有正确的实现递归和外部引用的。照高爷爷的说法,真的男人要能得到正确答案,不是男人的就得不到正确答案。当然,高爷爷当时自己也没有男人编译器,所以自己猜了一个-121,后来,真的男人编译器出来了,正确答案是-67。可见,高爷爷的人脑编译器,也不是男人编译器。(各位欲知详情的,猛点这个

      高级编程语言的发展历程(二)虚拟机的前世今生

      2009-6-13 原文链接 

      上节我们提到了 LISP 中,因为 eval 的原因,发展出了运行时环境这样一个概念。基于这个概念,日后发展出了虚拟机技术。但这段历史并不是平铺直叙的,实际上,这里面还经历了一个非常漫长而曲折的过程,说起来也是非常有意思的。本文我们就着重解释虚拟机的历史。

      我们21世纪的程序员,凡要是懂一点编程技术的,基本上都知道虚拟机和字节码这样两个重要的概念。所谓的字节码(bytecode),是一 种非常类似于机器码的指令格式。这种指令格式是以二进制字节为单位定义的(不会有一个指令只用到一个字节的前四位),所以叫做字节码。所谓的虚拟机,就是说不是一台真的计算机,而是一个环境,其他程序能在这个环境中运行,而不是在真的机器上运行。现在主流高级语言如 Java, Python, PHP, C#,编译后的代码都是以字节码的形式存在的,这些字节码程序,最后都是在虚拟机上运行的。

      虚拟机的安全性和跨平台性

      虚拟机的好处大家都知道,最容易想到的是安全性和跨平台性。安全性是因为现在可执行程序被放在虚拟机环境中运行,虚拟机可以随时对程序的危险行为,比如缓冲区溢出,数组访问过界等等进行控制。跨平台性是因为只要不同平台上都装上了支持同一个字节码标准的虚拟机,程序就可以在不同的平台上不加修改而运行,因为虚拟机架构在各种不同的平台之上,用虚拟机把下层平台间的差异性给抹平了。我们最熟悉的例子就是 Java 了。Java 语言号称一次编写,到处运行(Write Once, Run Anywhere),就是因为各个平台上的 Java 虚拟机都统一支持 Java 字节码,所以用户感觉不到虚拟机下层平台的差异。

      虚拟机是个好东西,但是它的出现,不是完全由安全性和跨平台性驱使的。

      跨平台需求的出现

      我们知道,在计算机还是锁在机房里面的昂贵的庞然大物的时候,系统软件都是硬件厂商附送的东西(是比尔·盖茨这一代人的出现,才有了和硬件产业分庭抗礼的软件产业),一个系统程序员可能一辈子只和一个产品线的计算机打交道,压根没有跨平台的需求。应用程序员更加不要说了,因为计算机很稀有,写程序都是为某一台计算机专门写的,所以一段时间可能只和一台庞然大物打交道,更加不要说什么跨平台了。真的有跨平台需求,是从微型计算机开始真的普及开始的。因为只有计算机普及了,各种平台都被广泛采用了,相互又不互相兼容软件,才会有软件跨平台的需求。微机普及的历史,比 PC 普及的历史要早10年,而这段历史,正好和 UNIX 发展史是并行重叠的。

      熟悉 UNIX 发展史的读者都知道, UNIX 真正普及开来,是因为其全部都用 C,一个当时绝对能够称为跨平台的语言重写了一次。又因为美国大学和科研机构之间的开源共享文化,C 版本的 UNIX 出生没多久,就迅速从原始的 PDP-11 实现,移植到了 DEC, Intel 等平台上,产生了无数衍生版本。随着跨平台的 UNIX 的普及, 微型计算机也更多的普及开来,因为只需要掌握基本的 UNIX 知识,就可以顺利操作微型计算机了。所以,微机和 UNIX 这两样东西都在 1970年 到 1980 年在美国政府、大学、科研机构、公司、金融机构等各种信息化前沿部门间真正的普及开来了。这些历史都是人所共知耳熟能详的。

      既然 UNIX 是跨平台的,那么,UNIX 上的语言也应当是跨平台的 (注: 本节所有的故事都和 Windows 无关,因为 Windows 本身就不是一个跨平台的操作系统)。UNIX 上的主打语言 C 的跨平台性,一般是以各平台厂商提供编译器的方式实现的,而最终编译生成的可执行程序,其实不是跨平台的。所以,跨平台是源代码级别的跨平台,而不是可执行程序层面的。而除了标准了 C 语言外,UNIX 上有一派生机勃勃的跨平台语言,就是脚本语言。(注:脚本语言和普通的编程语言相比,在能完成的任务上并没有什么的巨大差异。脚本语言往往是针对特定类型的问题提出的,语法更加简单,功能更加高层,常常几百行C语言要做的事情,几行简单的脚本就能完成)

      解释和执行

      脚本语言美妙的地方在于,它们的源代码本身就是可执行程序,所以在两个层面上都是跨平台的。不难看出,脚本语言既要能被直接执行,又要跨平台的话,就必然要有一个“东西”,横亘在语言源代码和平台之间。往上,在源代码层面,分析源代码的语法,结构和逻辑,也就是所谓的“解释”;往下,要隐藏平台差异,使得源代码中的逻辑,能在具体的平台上以正确的方式执行,也就是所谓的“执行”。

      虽说我们知道一定要这么一个东西,能够对上“解释”,对下“执行”,但是 “解释” 和 “执行” 两个模块毕竟是相互独立的,因此就很自然的会出现两个流派:“把解释和执行设计到一起”和“把解释和执行单独分开来”这样两个设计思路,需要读者注意的是,现在这两个都是跨平台的、安全的设计,而在后者中字节码作为了解释和执行之间的沟通桥梁,前者并没有字节码作为桥梁。

      解释和执行在一起的方案

      我们先说前者,前者的优点是设计简单,不需要搞什么字节码规范,所以 UNIX 上早期的脚本语言,都是采用前者的设计方法。我们以 UNIX 上大名鼎鼎的 AWK 和 Perl 两个脚本语言的解释器为例说明。AWK 和 Perl 都是 UNIX 上极为常用的、图灵完全的语言,其中 AWK, 在任何 UNIX 系统中都是作为标准配置的,甚至入选 IEEE POSIX 标准,是入选 IEEE POSIX 卢浮宫的唯一同类语言品牌,其地位绝对不是 UNIX 下其他脚本语言能够比的。这两个语言是怎么实现解释和运行的呢?我从 AWK 的标准实现中摘一段代码您一看就清楚了:

    int main(int argc, char *argv[])
    {
        ...
        syminit();
        compile_time = 1;
        yyparse();
        ...
        if (errorflag == 0)
        {
            compile_time = 0;
            run(winner);
        }
        ...
    }

    其中,run 的原型是:

    run(Node *a)    /* execution of parse tree starts here */

    而 winner 的定义是:

    Node *winner ;    /* root of parse tree */

      熟悉 Yacc 的读者应该能够立即看出,AWK 调用了 Yacc 解析源代码,生成了一棵语法树。按照 winner 的定义,winner 是这棵语法树的根节点。 在“解释”没有任何错误之后,AWK 就转入了“执行” (compile_time 变成了 0),将 run 作用到这棵语法树的根节点上。不难想像,这个 run 函数的逻辑是递归的(事实上也是),在语法树上,从根依次往下,执行每个节点的子节点,然后收集结果。是的,这就是整个 AWK 的基本逻辑:对于一段源代码,先用解释器(这里 awk 用了 Yacc 解释器),生成一棵语法树,然后,从树的根节点开始,往下用 run 这个函数,遇山开山,遇水搭桥,一路递归下去,最后把整个语法树遍历完,程序就执行完毕了。(这里附送一个小八卦,抽象语法树这个概念是 LISP 先提出的,因为 LISP 是最早像 AWK 这样做的,LISP 实在是属于开天辟地的作品!)Perl 的源代码也是类似的逻辑解释执行的,我就不一一举例了。

      三大缺点

      现在我们看看这个方法的优缺点。优点是显而易见的,因为通过抽象语法树在两个模块之间通信,避免了设计复杂的字节码规范,设计简单。但是缺点也非常明显。最核心的缺点就是性能差,需要资源多,具体来说,就是如下三个缺点。

      缺点1,因为解释和运行放在了一起,每次运行都需要经过解释这个过程。假如我们有一个脚本,写好了就不修改了,只需要重复的运行,那么在一般应用下尚可以忍受每次零点几秒的重复冗余的解释过程,在高性能的场合就不能适用了。

      缺点2,因为运行是采用递归的方式的,效率会比较低。我们都知道,因为递归涉及到栈操作和状态保存和恢复等,代价通常比较高,所以能不用递归就不用递归。在高性能的场合使用递归去执行语法树,不值得。

      缺点3,因为一切程序的起点都是源代码,而抽象语法树不能作为通用的结构在机器之间互传,所以不得不在所有的机器上都布置一个解释+运行的模块。在资源充裕的系统上布置一个这样的系统没什么,可在资源受限的系统上就要慎重了,比如嵌入式系统上。鉴于有些语言本身语法结构复杂,布置一个解释模块的代价是非常高昂的。本来一个递归执行模块就很吃资源了,再加一个解释器,嵌入式系统就没法做了。所以, 这种设计在嵌入式系统上是行不通的。

      当然,还有一些其他的小缺点,比如有程序员不喜欢开放源代码,但这种设计中,一切都从源代码开始,要发布可执行程序,就等于发布源代码,所以,不愿意公布源代码的商业公司很不喜欢这些语言等等。但是上面的三个缺点,是最致命的,这三个缺点,决定了有些场合,就是不能用这种设计。

      分开解释和执行

      前面的三个主要缺点,恰好全部被第二个设计所克服了。在第二种设计中,我们可以只解释一次语法结构,生成一个结构更加简单紧凑的字节码文件。这样,以后每次要运行脚本的时候,只需要把字节码文件送给一个简单的解释字节码的模块就行了。因为字节码比源程序要简单多了,所以解释字节码的模块比原来解释源程序的模块要小很多;同时,脱离了语法树,我们完全可以用更加高性能的方式设计运行时,避免递归遍历语法树这种低效的执行方式;同时,在嵌入式系统上,我们可以只部署运行时,不部署编译器。这三个解决方案,预示了在运行次数远大于编译次数的场合,或在性能要求高的场合,或在嵌入式系统里,想要跨平台和安全性,就非得用第二种设计,也就是字节码+虚拟机的设计。

      讲到了这里,相信对 Java,对 PHP 或者对 Tcl 历史稍微了解的读者都会一拍脑袋顿悟了:原来这些牛逼的虚拟机都不是天才拍脑袋想出来的,而是被需求和现实给召唤出来的啊!

      我们先以 Java 为例,说说在嵌入式场合的应用。Java 语言原本叫 Oak 语言,最初不是为桌面和服务器应用开发的,而是为机顶盒开发的。SUN 最初开发 Java 的唯一目的,就是为了参加机顶盒项目的竞标。嵌入式系统的资源受限程度不必细说了,自然不会允许上面放一个解释器和一个运行时。所以,不管 Java 语言如何,Java 虚拟机设计得直白无比,简单无比,手机上,智能卡上都能放上一个 Java 运行时(当然是精简版本的)。 这就是字节码和虚拟机的威力了。

      SUN 无心插柳,等到互联网兴起的时候, Java 正好对绘图支持非常好,在 Flash 一统江湖之前,凭借跨平台性能,以 Applet 的名义一举走红。然后,又因为这种设计先天性的能克服性能问题,在性能上大作文章,凭借 JIT 技术,充分发挥上面说到的优点2,再加上安全性,一举拿下了企业服务器市场的半壁江山,这都是后话了。

      再说 PHP。PHP 的历史就包含了从第一种设计转化到第二种设计以用来优化运行时性能的历史。PHP 是一般用来生成服务器网页的脚本语言。一个大站点上的 PHP 脚本,一旦写好了,每天能访问千百万次,所以,如果全靠每次都解释,每次都递归执行,性能上是必然要打折扣的。所以,从1999年的 PHP4 开始, Zend 引擎就横空出世,专门管加速解释后的 PHP 脚本,而对应的 PHP 解释引擎,就开始将 PHP 解释成字节码,以支持这种一次解释,多次运行的框架。在此之前, PHP 和 Perl,还有 cgi,还算平分秋色的样子,基本上服务器上三类网页的数量都差不多,三者语法也很类似,但是到了 PHP4 出现之后,其他两个基于第一种设计方案的页面就慢慢消逝了,全部让位给 PHP。WordPress 博客,也是基于 PHP 技术的,底层也是 Zend 引擎的。著名的 LAMP 里面的那个 P, 原始上也是 PHP,而这个词真的火起来,也是99年 PHP4 出现之后的事情。

      第二种设计的优点正好满足了实际需求的事情,其实不胜枚举。比如说 在 Lua 和 Tcl 等宿主语言上也都表现的淋漓尽致。像这样的小型语言,本来就是让运行时为了嵌入其他语言的,所以运行时越小越好,自然的,就走了和嵌入式系统一样的设计道路。

      结语

      其实第二种设计也不是铁板一块,里面也有很多流派,各派有很多优缺点,也有很多细致的考量,下一节,如果不出意外,我将介绍我最喜欢的一个内容:下一代虚拟机:寄存器还是栈。

      说了这么多,最后就是一句话,有时候我们看上去觉得一种设计好像是天外飞仙,横空出世,其实其后都有现实、需求等等的诸多考量。虚拟机技术就是这样,在各种需求的引导下,逐渐的演化成了现在的样子。

      高级编程语言的发展历程(三)FORTRAN 语言是怎么来的

      2009-7-2 原文链接

      在“高级语言是怎么来的”子系列的第一篇中,我们结合当时硬件的特点,分析了 FORTRAN 为什么一开始不支持递归。但是 FORTRAN 本身是怎么来的这个问题其实还是没有得到正面回答,本节我们就谈谈 FORTRAN 语言本身是怎么来的。

      其实,FORTRAN 语言也是现实驱动的。所以我们还是回到当时,看看当时程序员的需求和软硬件条件,看看 FORTRAN 是怎么来的。了解历史的另一个好处是,因为 FORTRAN 的发展历史正好和高级语言的发展历史高度重合,所以了解 FORTRAN 的背景,对于理解其他高级语言的产生都是大有帮助的。

      困难的浮点计算

      我们先从硬件的角度说起。大致从 1946 年第一台计算机诞生,到 1953 年,计算机一直都缺少两件非常重要的功能,一个叫浮点计算,一个叫数组下标寻址,这两个功能的缺失直接导致了高级语言的兴起。我们依次单个分析。读者对浮点计算应该都不陌生,用通俗的话说就是如 0.98×12.6 这样的实数乘法,或者 0.98 + 12.6 这样的实数加法的运算。用行话说,就是用计算机进行大范围高精度数的算术运算。

      学过二进制的同学都知道,二进制整数之间的乘法和加法等运算都是相对简单的,和正常的十进制运算是一样的,只是把加法和乘法这些基本操作用更加简单的逻辑或(OR) 和逻辑与 (AND) 实现而已,在电子电路上也很好实现。因此,就是世界上最早的电子计算机,ENIAC,也是支持整数的乘法加法等算术操作的。

      可是浮点运算就不一样了。因为一个额外的小数点的引入,在任何时候都要注意小数点的对齐。如果用定点计数,则计数的范围受到限制,不能表示非常大或者非常小的数。所以,浮点数一般都是用科学记数法表示的,比如 IEEE 754 标准。(不熟悉 IEEE 754 的读者也可以想像一下如何设计一套高效的存储和操作浮点数的规范和标准,以及浮点算法),科学记数法表示的浮点数的加减法每次都要对齐小数点,乘除法为了保持精度,在设计算法上也有很多技巧,所以说,相比较于整数的运算和逻辑运算,浮点运算是一件复杂的事情。落实到硬件上就是说,在硬件上设计一个浮点运 算,需要复杂的电路和大量的电子元器件。在早期电子管计算机中,是很少能做到这么大的集成度的。因此,不支持浮点也是自然的设计取舍。在计算机上放一个浮点模块这个想法,需要等电子工业继续发展,使得电子管体积小一点,功耗低一点后,才能进入实践。

      关于浮点计算的一些八卦

      关于浮点,这里顺带八卦一点浮点计算的事情。在计算机芯片设计中,浮点计算一直是一个让硬件工程师头疼的事情,即使到了386时代,386 处理器(CPU)的浮点乘法也是用软件模拟的,如果想用硬件做浮点乘法,需要额外购买一块 80387 浮点协处理器 FPU,否则就在 386 上做软件的模拟。这样做的原因在一块硅片上刻蚀一个 CPU 和一个 FPU 需要的集成度还是太高,当时的工艺根本没法实现。真的把 FPU 和 CPU 融在一起刻蚀到一块硅片上,已经是 1989 年的事情了。当时,Intel 把融合了 80386 和 80387 的芯片改了改,起了个名字叫 80486,推向了市场。带着浮点的处理器的普及,使得个人计算机能做的事情变多了。极度依赖于浮点计算的多媒体计算机(视频和声音等多媒体的压缩,转换和回放都是要依赖于浮点运算的),也正好随着 80486 的流行,逐渐普及开来。

      在处理器上融合浮点运算依然是困难的。即使到今天,很多低端的处理器,都不带有浮点处理器。所以,号称能够上天入地的,被移植到很多低端设备(比如手机)上的 Linux 内核,必然是不能支持浮点运算的,因为这样会破坏内核的可移植性。我们都知道,在内核模式下,为了保证内核操作的原子性,一般在内核从事关键任务的时候所有中断是要被屏蔽的,用通俗的话说就是内核在做事情的时候,其他任何人不得打扰。如果内核支持浮点运算,不管是硬件实现也好,软件模拟也罢,如果允许在内核中进行像浮点计算这样复杂而耗时的操作,整个系统的性能和实时响应能力会急剧下 降。即使是在硬件上实现的浮点运算,也不是件容易的事情,会耗费 CPU 较多的时钟周期,比如 Pentium 上的浮点数除法,需要耗费 39 个时钟周期才行,在流水线设计的 CPU 中,这种占用多个时钟周期的浮点运算会让整个流水线暂停,让 CPU 的吞吐量下降。在现代 CPU 设计中,工程师们发明了超标量,乱序执行,SIMD 等多种方式来克服流水线被浮点运算这种长周期指令堵塞的问题,这都是后话了。

      正因为对于计算机来说,浮点运算是一个挑战性的操作,但又是做科学计算所需要的基本操作,所以浮点计算能力就成了计算机能力的一个测试标准。我们常常听说有一个世界上前 500 台最快的超级计算机列表,这里所谓的“快”的衡量标准,就是以每秒钟进行多少次浮点计算(FLOPS) 为准。按照 Top500.org, 即评选世界上前 500 台超级计算机的机构2009年6月的数据,世界上最快的计算机,部署在美国能源部位于新墨西哥的洛斯阿拉莫斯国家实验室 (Los Alamos National Laboratory),当年造出第一颗原子弹的实验室。这台超级计算机,浮点计算速度的峰值高达 1456 TFlops,主要用来模拟核试验。因为美国的所有核弹头,海军核动力航母中的反应堆以及核试验,都由能源部国家核安全署(NNSA) 管理,所以能源部一直在投资用超级计算机进行核试验。在1996年美国宣布不再进行大规模的物理核试验后的这么多年,美国能源部一直用超级计算机来做核试验,所以在 Top500 列表中,美国能源部拥有最多数量的超级计算机。

      数组下标寻址之障

      言归正传,我们刚才说了在早期计算机发展史上,浮点计算的困难。除了浮点计算,还有一件事情特别困难,叫做数组下标寻址。用现代通俗的话 说,就是当年的计算机,不直接支持 A[3] 这样的数组索引操作,即使这个操作从逻辑上说很简单:把数组 A 的地址加上 3,就得到了 A[3] 的地址,然后去访问这个地址。

      这个困难在今天的程序员看来是不可思议的。为什么这么简单的数组下标寻址机制最一开始的计算机没有支持呢? 原来,当年的计算机内存很小,只有一千到两K的存储空间,所以,描述地址只需要几位二/十进制数(BCD)。从而,在每条指令后面直接加一个物理地址是可行且高效的寻址方式。这种寻址方式,叫做直接寻址,当时所有的机器,都只支持直接寻址,因为在机器码中直接指出操作数的准确地址是最简单直接的方法,计算机不需要任何复杂的地址解码电路。但坏处是,这个设计太不灵活了,比如说 A[3] 这个例子,就没法用直接寻址来表示。

      一般情况下,如果知道数组A, 对于 A[3] 这样的例子,用直接寻址问题去模拟间接寻址的困难还不是很大,只要程序员事先记住数组 A 的地址然后手工加上 3 就行了 (A也是程序员分配的,因为当时没有操作系统,所以程序员手工管理内存的一切)。可是,也有一些情况这样直接寻址是不行的。比如说,当时计算机已经能支持跳转和判断指令了,也就是说,可以写循环语句了。我们可以很容易看到, 以 i 为循环变量的循环体内,对 A[i] 的访问是不能写成一个静态的直接寻址的,因为 i 一直在变化,所以不可能事先一劳永逸的定好 A[i] 的所在位置,然后静态写在程序中。

      这样,即使写一个简单的 10×10 矩阵的乘法,程序员就不得不死写10的三次方即1000 行地址访问,而没办法用几行循环代替。当时的一些聪明人,也想了一些方法去克服这个问题,比如说,他们先取出 A 的地址,然后做一次加法,把结果,也就是当时 A[i] 的地址,注射到一个读内存的 LOAD 指令后面。然后执行那条 LOAD 指令。比如我要读 A[i],我先看,A的地址是 600,再看看 i 是3, 就加上 i,变成603,然后,把后面的指令改成 LOAD 603, 这样,就可以读到 A[i]。这个小技巧之所以可行,要感谢冯诺依曼爷爷的体系设计。在冯诺依曼计算机中,数据和程序是混在一起不加区分的,所以程序员可以随时像修改数据一样修改将要运行的下一条程序指令。就这样,靠着这个小技巧,好歹程序员再也不要用1000行代码表示一个矩阵乘法了。

      SpeedCoding 的出现

      计算机本来就是用来做数学计算的,可是科学计算里面最最基本的两个要素——浮点计算和数组下标访问,在当时的计算机上都缺少支持。这种需求和实际的巨大落差,必然会召唤出一个中间层来消弭这种落差。其实计算机科学的一般规律就是这样:当 A 和 C 相差巨大的时候,我们就引入一个中间层 B,用 B 来弥合 A 和 C 之间的不兼容。当年的这个中间层,就叫做 SpeedCoding,由 IBM 的工程师 John Backus 开发。

      SpeedCoding,顾名思义,就是让程序员编程更快。它其实是一个简单,运行在 IBM 701 计算机上的解释器。它允许程序员直接写浮点计算和下标寻址的指令,并且在底层把这些 “伪指令” 翻译成对应的机器码,用软件模拟浮点计算,自动修改地址等等。这样,程序员就可以从没完没了的手工实现浮点运算和下标寻址实现中解放出来,快速的编程。这 个 SpeedCoding,这可以算得上是 FORTRAN 的种子了。

      虽然这个解释器超级慢,程序员用这个解释器也用得很爽,也不感到它非常慢。这是因为当年计算机浮点计算都绕不过软件模拟,即使最好的程序员用机器码而不用这个解释器,写出来的程序,也不比这个解释器下运行快多少。另一个更加重要 的原因是,这个解释器极大的减少了程序员 debug 和 code 的时间。随着计算机速度的提高,当年一个程序耗费的计算成本和程序员编程耗费的人力成本基本上已经持平了。所以,相比较于写更加底层的机器码,用了 SpeedCoding 的程序员的程序虽然慢点,但人力成本瞬间降成 0,总体下来,用 SpeedCoding 比起不用来,总体成本还要低不少。

      好景不长,因为客户一直的要求和电子工业的发展,IBM 在 1954 年,终于发布了划时代的 704 计算机,很多经典的语言和程序,都首次在 704 上完成了。比如之前我们提到的 Steve Russell 的 LISP 解释器,就是在 704 上完成的。704 计算机一下子支持了浮点计算和间接下标寻址。这下用 SpeedCoding 的人没优势了,因为机器码支持浮点和下标寻址之后,写机器码比写 SpeedCoding 复杂不了多少,但是速度快了很多倍,因为 SpeedCoding 解释器太慢了,以前因为浮点和解释器一样慢,所以大家不在意它慢,现在浮点和寻址快了,就剩下解释器慢,写机器码的反而占了上风,程序员也就不用 SpeedCoding 了。

      FORTRAN 创世纪

      在 704 出来之前,做 SpeedCoding 的 John Backus 就认识到,要想让大家用他的 SpeedCoding, 或者说,想要从软件工具上入手,减少程序的开发成本,只有两个方法:

    • 程序员可以方便的写数学公式
    • 这个系统最后能够解析/生成足够的快的程序

      他认为,只有达到了这两点,程序员才会乐意使用高级的像 SpeedCoding 这样的工具,而不是随着硬件的发展在机器码和 SpeedCoding 这样的工具之间跳来跳去。他本人通过实现 SpeedCoding,也认识到如果有一个比机器码高级的语言,生产效率会高很多倍。那么,现在唯一的问题就是实现它,当然,这就不是一个小项目了,就需要 IBM 来支持他的开发了。 所以,在1953年,他把他的想法写成了一个文档,送给了 IBM 的经理。项目在 1954 年, 704 发布的当年,终于启动。John Backus 领导的设计一个能达到上面两点的编程系统的项目的成果,就是日后的 FORTRAN。

      和现在大多数编程语言不一样,FORTRAN 语言的设计的主要问题不是语法和功能,而是编译器怎么写才能高性能。John Backus 日后回忆说,当时谁也没把精力放在语言细节上,语言设计很潦草的就完成了(所以其后正式发布后又经过了N多修订),他们所有的功夫都是花在怎么写一个高性能的编译器上。这个高性能的编译器很难写,到 1957 年才写好,总共花了 IBM 216 个人月。等到 FORTRAN 一推出,不到一年的时间,在 IBM 总共售出的 60 台 704上,就部署了超过一半。现在没啥编程语言能够这么牛的攻城掠地了 :)

      结语 

      放到历史的上下文中看,FORTRAN 的出现是很自然的。一方面,复杂的数学运算使得一个能够表述数学计算的高级语言成为必须,计算机的发展也为这个需求提供了硬件条件;另一方面,随着计算机的发展,程序员的时间成本一直不变,但是计算的成本一直在降低,用高级语言和用机器码在性能上的些许差异变得可以忽略。这样的历史现实,必然会召唤出以少量的增加计算机工作量为代价,但能大幅度降低程序员时间成本的新的工具和设计。这种新的工具,新的设计,又对程序设计产生革命性的影响。在整个编程发展的 历史上,FORTRAN 和其他高级语言的出现可以说是第一波的革命;而后, UNIX和C语言的兴盛,使得系统编程的效率得到革命性提升,可以算是第二波革命;而面向对象方法,使得复杂的 GUI 等系统的编程效率得到提升,应该算得上是第三波革命。到如今,现在各种各样的方法论就更加多了,且看以后回看,哪种方法和工具能够大浪淘沙留下来。

      高级编程语言的发展历程(四)LISP 和 AI 的青梅竹马 A

      2009-8-31 原文链接

      LISP 语言的历史和一些番外的八卦和有趣的逸事,其实值得花一本书讲。我打算用三篇文章扼要的介绍一下 LISP 的早期历史。讲 LISP,躲不过要讲 AI (人工智能)的,所以干脆我就先八卦八卦他们的青梅竹马好了。

      翻开任何一本介绍各种编程语言的书,都会毫无惊奇的发现,每每说到 LISP,通常的话就是“LISP 是适合人工智能(AI)的语言”。我不知道读者读到这句话的时候是怎么理解的,但是我刚上大学的时候,自以为懂了一点 LISP 和一点人工智能的时候,猛然看到这句话,打死我也不觉得“适合”。即使后来我看了 SICP 很多遍, 也难以想象为什么它就 “适合” 了, 难道 LISP 真的能做 C 不能做的事情么?难道仅仅是因为 John McCarthy 这样的神人既是 AI 之父, 又是 LISP 之父, 所以 AI 和 LISP 兄妹两个就一定是很般配? 计算机科学家又不是上帝,创造个亚当夏娃让他们没事很般配干啥? 既然“本是同根生”这样的说法是不能让人信服的,那么到底这句话的依据在哪里呢? 我也是后来看 AI 文献,看当年的人工智能的研究情况,再结合当年人工智能研究的指导思想,当年的研究者可用的语言等历史背景,才完全理解“适合” 这两个自的。所以,这篇既是八卦,也是我的心得笔记。我们一起穿越到 LISP 和 AI 的童年时代。虽然他们现在看上去没什么紧密联系, 他们小时候真的是青梅竹马的亲密玩伴呢!

      让机器拥有智能,是人长久的梦想,因为这样机器就可以聪明的替代人类完成一些任务。二战中高速电子计算机的出现使得这个梦想更加近了一步。二战后,计算机也不被完全军用了,精英科学家也不要继续制造原子弹了,所以,一下子既有资源也有大脑来研究 “智能机器”这种神奇的东西了。我们可以随便举出当年研究繁盛的例子:维纳在 1948 年发表了《控制论》,副标题叫做 《动物和机器的控制和通信》,其中讲了生物和机器的反馈,讲了脑的行为。创立信息论的大师香农在 1949 年提出了可以下棋的机器,也就是面向特定领域的智能机器。同时,1949年,加拿大著名的神经科学家 Donald Hebb 发表了“行为的组织”,开创了神经网络的研究;图灵在 1950 年发表了著名的题为“计算的机器和智能”的文章,提出了著名的图灵测试。如此多的学科被创立,如此多的学科创始人在关心智能机器,可见当时的确是这方面研究的黄金时期。

      二战结束十年后,也就是 1956 年,研究智能机器的这些研究者,都隐隐觉得自己研究的东西是一个新玩意,虽然和数学、生物、电子都有关系,但和传统的数学、生物、电子或者脑科学都不一样,因此,另立一个新招牌成了一个必然的趋势。John McCarthy 同学就趁着 1956 年的这个暑假,在 Dortmouth 大学(当年也是美国计算机科学发展的圣地之一,比如说,它是 BASIC 语言发源地), 和香农、Minsky 等其他人(这帮人当年还都是年轻人),一起开了个会,提出了一个很酷的词,叫做 Artificial Intelligence,算是人工智能这个学科正式成立。因为 AI 是研究智能的机器,学科一成立,就必然有两个重要的问题要回答,一是你怎么表示这个世界,二是计算机怎么能基于这个世界的知识得出智能。第一点用行话说就 是“知识表示”的模型,第二点用行话说就是“智能的计算模型”。别看这两个问题的不起眼,就因为当时的研究者对两个看上去很细微的问题的回答,直接造就了 LISP 和 AI 的一段情缘。

      我们各表一支。先说怎么表示知识的问题。AI 研究和普通的编程不一样的地方在于,AI 的输入数据通常非常多样化,而且没有固定格式。比如一道要求解的数学题,一段要翻译成中文的英文,一个待解的 sodoku 谜题,或者一个待识别的人脸图片。所有的这些,都需要先通过一个叫做“知识表示”的学科,表达成计算机能够处理的数据格式。自然,计算机科学家想用一种统 一的数据格式表示需要处理多种多样的现实对象,这样,就自然的要求设计一个强大的,灵活的数据格式。这个数据格式,就是链表。

      这里我就不自量力的凭我有限的学识,追寻一下为啥链表正好成为理想的数据结构的逻辑线。我想,读过 SICP 的读者应该对链表的灵活性深有感触。为了分析链表的长处,我们不妨把他和同时代的其他数据结构来做一比较。如我在前面所说,当时的数据结构很有限,所以我们不妨比较一下链表和同时代的另一个最广泛使用的数据结构——数组——的优劣。我们都知道,数组和链表都是线性数据结构,两者各有千秋,而 FORTRAN 基本上是围绕数组建立的,LISP 则是围绕链表实现的。通过研究下棋,几何题等 AI 问题的表示,我们的读者不难发现, AI 研究关心于符号和逻辑计算远大于数值计算,比如下棋,就很难抽象成一个基于纯数字的计算问题。这样,只能存数字的数组就显得不适合。当然我们可以把数组扩展一下,让这些数组元素也可以存符号。不过即使这样,数组也不能做到存储不同结构的数据。比方说棋类中,车马炮各有各自的规则,存储这些规则需要的结构和单元大小都不一样,所以我们需要一个存储异构数据单元的模块,而不是让每个单元格的结构一样。加上在AI 中,一些数据需要随时增加和修改的。比如国际象棋里,兵第一步能走两步,到底部又能变成皇后等等,这就需要兵的规则能够随时修改、增加、删除和改变。其他问题也有类似的要求,所有的这些,都需要放开数组维度大小一样的约束,允许动态增加和减少某一维度的大小,或者动态高效的增加删除数组元素。而一旦放开了单元格要同构和能随时增加和删除这样两个约束,数组其实就不再是数组了,因为随机访问的特性基本上就丢失了,数组就自然的变成了链表,要用链表的实现。

      所以,用链表而不是数组来作为人工智能的统一的数据结构,固然有天才的灵机一动,也有现实需求的影响。当然,值得一提的是,在 Common LISP 这样一个更加面向实践而不是科学研究的 LISP 版本中,数组又作为链表的补充,成了基本的数据结构,而 Common LISP,也就能做图像处理等和矩阵打交道的事情。这个事实更加说明,用什么样的数据结构作为基本单元,都是由现实需求推动的。

      当然,科学家光证明了列表能表示这些现实世界的问题还不够,还要能证明或者验证额外的两点才行。第一点是列表表示能够充分的表示所有的人工智能问题,即列表结构的充分性。只有证明了这一点,我们才敢放心大胆的用链表,而不会担心突然跳出一个问题链表表达不了;第二是人工智能的确能够通过对列表的某种处理方法获得,而不会担心突然跳出一个人工智能问题,用现有的对链表的处理方法根本没法实现。只有这两个问题的回答是肯定的时候,列表处理才会成为人工智能的一部分。

      对于这两个问题,其实都并没有一个确定的答案,而只是科学家的猜想,或者说是一种大家普遍接受的研究范式(paradigm)。 在 1976 年,当年构想 IPL(Information Processing Language),也就是 LISP 前身的两位神人 Alan Newell 和 Herbert Simon,终于以回忆历史的方式写了一篇文章。在这篇文章中,他们哲学般的把当时的这个范式概括为:一个物理符号系统对于一般智能行为是既充分又必要的( A physical symbol system has the necessary and sufficient means for general intelligence action)。用大白话说就是,“智能必须依赖于某种符号演算系统,且基于符号演算系统也能够衍生出智能”。在实践中,如果你承认这个猜想,或者说这个范式,那你就承认了可以用符号演算来实现 AI。于是,这个猜想就让当时几乎所有的研究者,把宝押在了实现一个通用的符号演算系统上,因为假如我们制造出一个通用的基于符号演算的系统,我们就能用这个系统实现智能。

      上面我们说过,链表的强大的表达能力对于这个符号演算系统来讲是绰绰有余的了,所以我们只要关心如何实现符号演算,因为假如上面的猜想是对的,且链表已经能够表示所有的符号,那么我们的全部问题就变成了如何去构建这样的符号演算系统。后面我们可以看到,LISP 通过函数式编程来完成了这些演算规则的构建。

      这里,需要提请读者注意的是,LISP 的全称是 LISt Processing,即列表处理,但实际上 LISP 是由两种互相正交的哲学组合形成的,一个是列表处理,另一个是函数式编程。虽然在下面以后,我们会介绍 S-Expression 这样美妙的把两者无缝结合在一起的形式,但是为了清晰我们的概念,我要强调一下列表处理和函数式编程是两个正交的部分。实际上,我们完全可以用其他的不是函数的方式构建一个列表处理语言。在历史上,早在 FORTRAN 出现之前,Alan Newell 和 Herbert Simon 就用汇编实现了一个叫 IPL 的语言,而这个 IPL 语言就是面向过程的对列表处理的,而后,McCarthy 一开始也是用一系列的 FORTRAN 子程序来做列表处理的。比如 LISP 里面的 CAR 操作,其全称实际上是 Content of the Address portion of the Register, 顾名思义,寄存器的地址单元内容,也即列表的第一个元素(和C表达数组的方式类似,这里寄存器中存着指向列表第一个元素的指针)。 函数式的却不以列表为基本数据单元的语言也很多,比如 Scala ,就是以对象为基本数据单元。 因此,函数式和列表处理是不一定要互相耦合的。 那么,到底是什么原因使得 LISP 选择函数式,这样的选择又为啥更加适合当时 AI 的研究呢,我们下节将继续介绍当时 AI 的研究范式,强弱 AI 之间的辩论和函数式编程在当年 AI 研究上的优点。

      高级编程语言的发展历程(五)LISP 和 AI 的青梅竹马 B

      2010-2-10 原文链接

      上回我们说到 LISP 和 AI 很是青梅竹马的时候,浮光掠影地说因为 LISP 的基本数据单元——”链表”在知识表示上的比较优势。我们说, AI 要处理的数据结构和要刻画的现实世界的模型很复杂,使得数组等其他简单数据结构不能胜任,所以“链表”成了最佳的选择。如果我们顺着这样的逻辑线往下看, 似乎选择 LISP 这个“列表处理的语言”似乎是理所当然的。可是,这个原因并不充分。因为 LISP 语言可不仅仅是列表处理,还包括函数式编程等等其他。反过来说,如果仅仅是列表处理对于 AI 至关重要的话,那么在 FORTRAN 和 ALGOL 这些通用编程语言又非常普及的传统语言里面写一些关于列表处理的函数岂不是更加直观和方便? 归根结底,到底 LISP 还有什么其他奥妙呢?

      当我们追寻函数式编程这条线索的时候,就会不可避免的触及到 AI 的早期历史。LISP 的特性,其实都是和当时 AI 的范式 (paradigm) 息息相关的。

      AI 范式的演变

      早在 McCarthy 这一代人提出 AI 之前,冯诺伊曼等人就开始研究什么是智能以及如何实现智能的问题了。所不同的是,他们更加偏重于研究大脑的内部工作机理,并且试图通过在模拟大脑的工作机理,来实现智能。这一学派的哲学很清晰: 人类大脑是一个标准的智能体,我们只需要让计算机模拟人的大脑的工作方式,计算机就有了和人类大脑一样的智能了。对于这一派的研究者来说,他们相信大脑的结构和工作机理决定了智能,至于大脑是用脑细胞构成的,还是用电子电路模拟的,对于智能来说毫不重要。这方面的著名工作就是冯·诺伊曼的《计算机和大脑》这篇论文。在这篇不算很学术的随笔里面,他分析了人的大脑有多少个神经元,计算机有多少个晶体管,通过这些定量的比较来解释计算机和人的大脑的差距。当时和冯·诺伊曼齐名的另一个神童是开创控制论的维纳。他和冯·诺伊曼一样,兼通很多学科。和冯·诺伊曼一样,他职业是数学家,但是也精通如神经科学和脑科学等学科。 一个显然的例子就是在《控制论》这本书里面, 维纳对大脑和神经的分析比比皆是。这种对大脑和神经分析的传统,从 Cajal (对,就是写 Advice for a Young Investigator 的那个大神))开始,一直延续到了后来 AI 中的联接主义(主要研究神经网络的一个人工智能学派)。

      可是,从脑科学和认知科学的角度去分析智能在当时有一个非常大的局限: 脑神经解剖学本身不成熟。比方说,现如今脑科学家在分析脑功能的时候一般会借助于 fMRI 和其他一些神经造影技术。这些技术可以做到实时观测到脑中血氧分布,直接确定大脑在执行特定任务时候的活跃部分。当年的科学家则只能使用有限的几种医学成 像技术,或者从血管摄影的角度研究大脑。受限于当时的研究条件,当年的研究者很难直接观测到脑神经的实时工作状态,分析大脑的实时工作机理。因此,对脑的研究就很难做到非常深刻。医学研究条件的限制,加上当时电子学的发展和集成度远远不够,用电子电路模拟大脑生成智能就显得非常遥远。因此,虽然这一派的思想超前,但是大部分的工作都不在于真正的用电子电路模拟大脑,而是在探索脑科学和神经科学本身,或者仅仅用电子电路模拟一些简单的神经动力学行为和小规模神经网络。正是因为连接主义在实现人工智能本身方面进展并不大,所以在AI领域中一直不是潮流的研究方向。上个世纪 80 年代前成功实施的一些人工智能系统,极少是来自于连接主义学派的。直到80年代后随着 BP 算法的重新发现,联接主义才迎来了第二春。这时候,LISP 已经过完 20 岁生日了。所以,联接主义 对 AI 领域使用的编程语言的选择的影响并不算大。

      符号主义

      虽然联接主义这一学派在当时不怎么流行,当年的 AI 研究可是进行的如火如荼。这如火如荼的学派,采用的是另外一套方法,我们称之为“符号主义学派”。符号主义学派的渊源,可以直接追溯到图灵。图灵在人工智能方面做过很多的研究,其中最为大家所知的就是“图灵测试“了。有句俗话叫做“在网上,没人知道你是一条狗”, 在这句话里,只要把“狗”换成“计算机”,就是简单版的图灵测试了。用个比较“潮”的比方,图灵测试就是让一台计算机或者一个真实的人(又叫评委)在网上交流,然后让这个评委猜测和他交谈的究竟是人还是计算机。如果这位评委也不能分辨谈话的对方到底是人还是计算机的话,我们就认为这个计算机已经足以“以假乱真”,拥有“和人类一样的智能”了,也就是通过“图灵测试了”。

      在很长一段时间里,图灵测试一直是人工智能研究的圣杯(holy grail)。也就是说,通过”图灵测试“ 成了人工智能研究的终极目标。那么,自然的,最最直接的通过图灵测试的方法不是让计算机和人脑一样思考,而是只要能够让计算机处理对话中用到的的单词、句子和符号,并且在对话中能够和人一样的操纵这些单词和符号,计算机就有很大的希望通过图灵测试。从最极端的情况来看,计算机甚至都不需要去“理解”这些句子的含义,都有可能通过图灵测试。[具体细节可以阅读 Wikipedia 上的“Chinese Room (中文房间)”条目]。有一个开源的聊天机器人,叫做 A.L.I.C.E., 就把上面我们说的“只要能够处理和操纵符号,就有可能通过图灵测试”发挥到了近乎极致。这个聊天机器人在图灵测试比赛中已经多次骗过人类评委,到了非常 “智能”几乎能以假乱真的地步。可是,就是这样一个离通过图灵测试很近的机器人,其基本结构却简单到了我们都不能想像的地步:A.L.I.C.E. 的数据库里面有一条一条的规则,这些规则规定了她看到你说什么的时候她说什么。唯一有点“智能”的地方,就是有些规则不光取决于你这句话,还取决于你的上一句话。[比如日常对话中我们先问“你喜欢看电影么?”,接着再问“什么类型的?”,这时候就需要前一句话推出这个问题是“(喜欢)什么类型的(电 影)”]。“中文房间”的例子,和 A.L.I.C.E. 机器人如此简单的结构,都出人意料的显示出,即使计算机拥有了对符号的操作能力,通过了图灵测试,它也未必是“有智能”的。可惜这句话只是我的马后炮而已,在 AI 发展的早期,因为图灵测试的拉动,联接主义的相对弱势和符号主义的繁盛,都把全世界的 AI 研究往一个方向拽,这个方向,很自然的,就是“符号处理”。

      符号处理和 LISP 补充

      其实上一篇我们已经提到了,Alan Newell 和 Herbert Simon 认为对符号演算系统就可以衍生出智能,所以上面的文字,算是对符号主义这个 paradigm 做一个历史的小注解。当我们把 LISP 放到这段历史中,就会自然的想到, 什么语言适合人工智能的问题,就变成了“什么语言能做符号处理”。这个问题的答案,读者也都猜到了,就是 LISP。

      符号处理在 LISP 里面的长处前文我已经介绍过一些了,这里我们可以再补充几点零碎的。LISP 里有一个大家都知道的统一表示程序和数据的方法,叫做 S-Expression。这个 S,其实就是 Symbolic 的意思。把程序和数据都统一的当成符号,用现代编程语言的话说,就是 LISP 支持 meta-programming。LISP 程序可以处理,生成和修改 LISP 程序。这个特性,加上函数是一阶对象的特性,使得 LISP 远远比同时代的任何语言灵活。我本人不是 LISP 的用户(初级用户都算不上),因此在这一点上所知有限。但单就我有限的对 LISP 的理解,我认为 LISP 的这种灵活,恰好满足了基于符号处理的 AI 领域对语言的“强大的表达能力”(可以对任何复杂系统建模)和“高层的抽象能力” 的需求。关于第一点,有一个著名的段子,说任何一门编程语言技巧和思想被提出的时候,总会有一个高人出来,说,这个玩意儿在 LISP 里面早就有了,具体的例子包括刚才说的 metaprogramming, object oriented language。这里面蕴含的,就是 LISP 的强大的表达能力,使得很多编程的范式,在 LISP 里面都能实现,或者找到影子。关于第二点,SICP 中例子比比皆是,讲得都比我深刻许多,就无需废话了。

      在上篇文章中我提到,翻开任何一本编程的书,都会讲到“LISP是适合 AI 的编程语言”。那么,如果您和我当年一样,有兴趣从事 AI 方面的研究和探索,就不免要疑惑:“为了学习 AI, 我要不要学习 LISP” 呢?现在距离我当年的这个疑惑已经差不多8年了,我并没有一个确定的答案,但是我知道了更多的一些事实。

      如今的 AI 范式

      如果你让任何一个 AI 方向的研究者推荐几本适合初学者的书的话,十有八九他会提到 “Artificial Intelligence: A Modern Approach”(人工智能,一种现代方法) 和 “Artificial Intelligence: A New Synthesis” (人工智能,一个新的综述)。这两本书的作者分别是 Peter Norvig 和 Nils Nilsson,都是 AI 领域的元老级人物。如果你是一个对书名很敏感的人,你肯定会想:奇怪了,这种书又不是畅销书,难道这两位大牛写了书怕卖不出去,非要在书名上加一个 “现代” 或者 “新” 来吸引眼球? 事实上,这个“现代”和这个“新”都大有来头。实际上,这二十年来,AI 研究领域接连发生了好几个非常大的 paradigm shift. 传统的基于符号的 AI 方法不再是主流,取而代之的,是多种多样的基于统计的,基于自动推理的,基于机器学习的,基于群体智慧的,基于大规模数据集的等等各种各样研究方法的兴起。这个 paradigm shift, 对于领域之外的人好像是静悄悄的,可实际上 AI 领域早已发生了翻天覆地的变化。所以才会有 “新” 和 “现代” 这样的词出现在书标题上。不幸的是,大多写编程语言书的作者,未必全部知晓这个变化,因此还沿袭原来的框架,继续写下 “LISP是适合 AI 的编程语言” 这样一个早就不能完全反映现状的断言。如果我们统计一个从事 AI 研究的研究者或者科学家用什么语言,答案可能是五花八门无所不有: 做 AI Search 的用 C/C++/Java, 做机器学习的如果模型和矩阵关系密切,可以用 Matlab, 如果统计计算较多,也可以用 R。至于做数据挖掘等等,语言和库更加五花八门,根本无法宣称那一个语言占上风。LISP 是适合 AI 的语言的教科书神话,也早就被无数的这样的实例给打破了。(延伸阅读:Why is Lisp used for AI?

      高级编程语言的发展历程(六)SCHEME 语言是怎么来的

      2010-7-12 原文链接

      导言

      Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言(实际上 SICP 的作者就是 Scheme 的作者)。虽然 Scheme 本身只是一个精简化的适合教学的语言,可它首先提出的一些重要的思想,引领了新一代的 LISP 语言的出现。实际上, LISP 语言发展的历史是连续的,之所以我在这里人为的把 LISP 的发展史划分为上一代和现代,是因为随着 Scheme 首次引入并规范化了一些重要概念, LISP 语言出现了很多以前从来没有大规模普及的新特性。以 Common LISP 为代表的 LISP 语言也因为这些新特性,而焕发了第二春。人所共知的 Paul Graham 大叔,借着这一波 LISP 复兴的浪潮,不光写出了 On Lisp 这样的好书;而且还用 Common LISP 写出了一个在线电子商务平台,在 1998 年的时候以近 5 千万美元的价格卖给了 Yahoo! (凭借这笔买卖, Paul 大叔现在经营着 Y Combinator 天使投资,成为硅谷著名的天使)。前段时间卖给 Google 的 ITA,负担着世界上大部分的航班资讯查询,核心系统也是 Common LISP。虽然不该把 Common LISP 的很多成就全部归结到 Scheme, 但 Scheme 作为一个重要的历史分水岭,探究一下它的历史来源还是很有趣的。

      函数作为一级对象

      我们都知道 LISP 是一个函数式的编程语言。在 LISP 中,函数是一种基本类型。类比的看,C 家族的语言中,整数是一个基本的类型,所以,整数类型的变量既可以作为参数传递给一个函数,也可以作为返回值返回。比如,两个整数求和这个函数,用 C 家族的语法就是:

    int add(int a, int b);

      因为在 LISP 里面,函数也成了基本类型。如果我们有一个 add 函数如下:

    (define (add x y) (+ x y))

      显然,它在 LISP 里就和 C 里的 int 一样,能够作为参数传递给其他函数。

      函数作为参数在 LISP 里非常普遍。我们知道著名的 APPLY MAP 和 REDUCE 这三个“高阶”函数(所谓高阶的意义就是参数可以是函数)。其中 APPLY 的最基本形式可以带两个参数,第一个参数是函数,第二个参数是一个列表。APPLY 的效果就是把这个列表作为参数表,送给第一个参数所代表的函数求值。如果我们在 LISP 里面用 APPLY(add, (1, 2)) 结果就是3,即把 (1,2) 送给add 作为参数,结果自然是 3。这是函数作为参数的例子,还有函数作为返回值的例子就不一一列举了。

      自由变量的幽灵

      在 add 这个函数的定义中我们可以看到,它的结果和两个输入值 x, y 有关。如果我们用 add(1,2) 调用 add 函数, 我们至少期望变量 x 会被赋值为 1, 变量 y 被赋值为 2。而结果 (+ x y) 则相应的为 3。在这个简单的例子中, 显然,如果 x 和 y 有一个值不知道的话, (+ x y) 的计算就无法完成。我们暂且把这些对函数求值不可缺少的变量称之为“必要变量”。显然,这些必要变量的值是需要确定的,否则函数无法进行求值。在我们 add 函数的例子里,x, y 这两个变量既是全部的必要变量,又是这个函数的参数,所以这个函数的结果就完全由输入的 x, y 的值确定。可以想象,任何一个像 add 这样的所有的必要变量都是来自于输入参数的函数,不论在什么地方被调用,只要输入的参数值一样,输出的结果必然一样。

      如果现实中所有的函数都有上面的性质的话,那就没有这篇文章了。可惜的是我们很快发现有好多函数不符合上面我们说的“输入的参数值一样,输出的结果必然一样”这个结论。我们甚至无须用 LISP 里面的例子来说明这一点。用 C 语言的都知道,取系统当前时间的函数 time,以及取随机数的函数 rand, 都是不需要输入值(0个输入参数)。因此任何时候这两个函数被调用的时候,我们都可以认为输入值一样(为 void 或 null)。但我们在不同的时间调用 time 或者多次调用 rand,很显然可以知道他们输出的结果不可能每次一样。

      函数式编程里面有更多的类似的例子。这些例子说明了的确有些函数,对于同样的输入值,能够得到不同的结果。这就很显然的表明,这些函数的必要变量中,有些不是函数的输入参数或者内部变量。我们把这些变量,叫做自由变量(free variable) [相反的那些被成为受限变量(bounded variable)]。这里的自由和受限,都是相对函数讲的,以变量的取值是不是由函数本身决定来划分的。

      虽然自由和受限变量是函数式语言里面的概念,但在命令式语言中也有影子。比方说,C 语言中,函数中用到的全局变量就是自由变量;在 Java 程序中,匿名内部类里面的方法可以用到所在外部类中的成员变量或者所在方法里标记为 final 的那些变量。这些可以被内部类的方法访问的,又不在内部类方法的参数表里面的变量都是自由变量。乐意翻看 GNU C Library 的好奇读者会看到,GNU libc 中的 rand 函数就用到了一个 random_data 的变量作为自由变量 (glibc/stdlib/random.c)。time 也是一样,通过一个系统调用来设置时间,而这在原理上等价于用到一个叫做”当前时间”的自由变量 (glibc/stdlib/time/time.c)。

      我们知道,在高级语言里面仅仅设计或者加入一个特性不难,难的是让所有的特性能协调一致的工作。比方说 Java 语言假设一切均为为对象,容器类型也假设装着对象,但是 int 类型却不是对象,让无数程序员为装箱拆箱大汗淋漓。回到 LISP, 当函数允许自由变量,函数有能够被作为参数传来传去的时候,自由变量的幽灵就随着函数作为参数传递而在程序各处游荡。这就带来了两个问题,一个涉及到自由变量的值的确定机制,另一个涉及到这个机制的实现。

      两种作用域

      为了说明自由变量的幽灵和作用域,我们还是从一个例子入手。假设我们要一个做加 n 的函数。为了体现出自由变量,我们把它写成

    (define (addn s) ( lambda x (+ x s)))

      这个函数本身没什么特别的:输入一个 s, 输出一个 对任意 x 返回 x+s 的函数。注意到这个函数的“返回值”是一个函数。基于这个 addn 函数,我们可以定义 +1 函数 add1 函数如下

    (define (add1 s) ((addn 1) s))

      这个也很好解释,如果输入一个 s, (addn 1) 返回了一个加一函数,这个函数作用在 s 上,即可得到 s+1。一切看上去很顺利,直到我们用一个 Scheme 出现前的 LISP 解析器去计算 (add1 4)。我们期望得到的值是 5, 而它给你的值可能是 8。怎么回事?

      为了解释这个 8 的来源,我们可以模拟一下一个基于栈的解释器的工作过程。(add1 4) 调用首先将参数 s 赋值为 4 然后,展开 add1 函数,即将 s=4 压栈,计算 (addn 1)。在调用 addn 时。s 又作为了 addn 的形式参数。因此,按照基于栈的解释器的标准做法,我们在一个新的活动窗口中将 s =1 压栈。addn 这个函数返回的是一个 “lambda x (+ x s)” 的函数,其中 s 是自由变量。然而一旦 addn 返回,栈中的 s=1 就会被弹出。当我们把这个返回了的 lambda 表达式作用到 4 上求值时候,x 是这个 lambda 表达式传入的形式参数,赋值为 4,栈里面的 s 的值 只有 s=4, 因此 (+ x s) 得到的是 8。

      这显然不是我们想要的。总结这个结果错了的原因,是因为我们的解释器没有限定 lambda x (+ x s) 里面的自由变量 s 为 1。而是在计算这个 lambda 表达式的时候才去查找这个自由变量的值。自由变量的幽灵在函数上开了一个后门,而我们没有在我们想要的地方堵上它,让它在函数真正求值的时候泄漏出来。

      我们不是第一个发现这个问题的人。实际上, LISP 刚出来没多久,就有人向 LISP 的发明人 John McCarthy 报告了这个 “BUG”。John 也认为这是一个小 BUG,就把球踢给了当时写 LISP 实现的 Steve Russell。此人我之前介绍过,乃是一个水平很高的程序猿(Code Monkey)。他认识到,这个问题的来源,在于返回的 lambda 表达式失去了不应该失去的确定它自由变量值的环境信息,在求值的时候,这些环境信息应该跟着这个 lambda 表达式一起。这样才能保证没有这个 BUG。不过 lambda 表达式在 LISP 语言中已经成型了,所以他就引入了一个新叫做 FUNCTION 的修饰符。作为参数的 lambda 表达式或函数要改写成 (FUNCTION lambda) 。这样,这个 lambda 表达式在被 eval 解析的时候就会被标记成 FUNARG,并且静态绑定到解析时所在环境。而用 APPLY 对函数求值时,有 FUNARG 标签的函数会在当时绑定的环境中求值,而不是在当前环境中求值。自由变量没有到处乱跑,而是被限制在了当时绑定的环境里面。Russell 的这个巧妙设计,成功关闭了自由变量在函数上开的口。这种加上了环境的函数就既能够被四处传递,而不需要担心自由变量的幽灵到处乱串。这个东西,后来就被 称为“闭包”。Russell 用 FUNCTION,以用一种“装饰”的方式,在 LISP 1.5 中第一次引入和实现了闭包。

      在编程语言的术语中,上面的让自由变量自由自在的在运行时赋值的机制,一般叫做动态作用域(dynamic scope),而让函数和确定自由变量值在解析时静态绑定的机制,一般称之为静态作用域(static dynamic scope)。既然是静态绑定的环境是解析的时候确定的,而解析器是逐行解析程序的,所以,静态作用域的环境是完全由程序代码的结构确定的。因此有时候静态作用域又被等价的称为“文法作用域”(lexical scope)。上面我们的例子里。我们的真正意图是使用静态作用域,却遇到了一个使用动态作用域的 LISP 解析器,因此出现了 (add1 4) 等于 8 的错误。但这个问题并不足以说明静态作用域一定好。动态作用域的问题,关键在于违反了 Alpha 变换原则和封装原则,不过不在此详细展开了。

      高级编程语言的发展历程(七) LISP 语言前传

      2011-9-27 原文链接

      Lisp 的主要设计者 John McCarthy 曾经就 Lisp 的发展史,专门写过一篇 History of Lisp 的文章。这里介绍的历史,基本史实部分参照了 John McCarthy 的这篇文章,以及同时期 MIT 的关于 Lisp 的技术报告。

      Lisp 的历史要从 IBM 的神奇机器 704 说起。此时是 1954 年,尽管距离 1946 年第一台计算机 ENIAC 的出现已经八年了,商用计算机市场还仅仅起步。很早就进入计算机市场的 IBM 做出了一个影响深远的决定:发布一台可以进行浮点计算的,面向科学和工程的电子计算机。这台计算机,很朴素地跟着 IBM 之前发布的 701,702 后,被编号成 704(不知为什么 IBM 从来没公布过 703)。说 704 是神奇机器,是因为这台机器在计算机科学发展史上意义重大:世界上最早的语音合成程序就是由 Bell 实验室的科学家在 IBM 704 上完成的。 Fortran,Lisp 也是最早在 IBM 704 上实现的。

      和当年的所有计算机一样,IBM 704 是个百万美元级别的大玩具,不是一般人甚至一般大学能够买得起的。好在 IBM 和大学的关系一向很紧密,在 1957 年的时候,决定捐一台 704 给 MIT。当时在 Dartmouth 教书的 John McCarthy 和在 MIT 教书的 Marvin Minsky 关系很好,因此这台即将到达的 704,即将成为 McCarthy 的新玩具。

      当年部署一台计算机的周期很长,为了不让自己闲着,McCarthy 决定一边等机器部署,一边研究一下如果有了这台机器,可以做点什么。当时 Minsky 手里有一个 IBM 的项目,内容是使用计算机证明平面几何问题。既然计算机没来不能写程序,他们就只能从抽象的层面思考问题的解决方法。这个思考的结果,是开发一套支持符号计算的 FORTRAN 子系统。他们的基本想法是,用一系列的 FORTRAN 子程序,来做逻辑推理和符号演绎。

      回头看,这条路的确绕开了没有 704 就写不了程序的路障。因为我们只需要大致了解 Fortran 能够做什么,不能做什么,无需实际 Fortran 编程,就可以假想我们已经有了一系列未来可以实现的子程序,然后只要在数学上证明这些通过子程序的组合,加上自动逻辑推理,就可以证明平面几何定理。这就把一个计算机工程学问题,抽象成了一个数学问题(日后这个领域被正式划归到人工智能的学科中,但在当时这还是属于数学问题)

      这样,计算机没来之前,McCarthy 的最终结果,是一个用 Fortran 子程序做列表处理的简单系统。McCarthy 的这条路很现实的做法——如果不用 Fortran 而是自己写一个新的语言的编译器话,可能需要好几年的时间。而 McCarthy 当年还不是终身教授,投入到写作新语言这样旷日持久且不能保证成果的项目中去,不会对他的职业生涯有太大的正面作用。

      704 送到 MIT 后, McCarthy 带着两个研究生,将之前计划的 Fortran 列表处理子程序实现了,并命名为 Fortran 列表处理语言 (FLPL) 。然而,因为 Fortran 语言本身的限制,McCarthy 对 FLPL 并不满意。他在写作自动求函数导数的程序时[读过 SICP 的读者会发现这是一道习题],发现 FLPL 的弱点集中体现在两个地方。

      第一个问题是递归。我们在 Fortran 语言怎么来的这一节已经提到,704 上的 Fortran 语言是不支持递归的。而 McCarthy 心中所设想的语言,很重要的一条就是递归:没有递归,很多列表处理的函数只能用循环来实现,而循环本身并不是 McCarthy 设想的语言的一部分。熟悉函数求导的链式法则的读者可以想像,没有递归的求导程序是何其难写,因为函数求导过程本身就是递归定义的。

      第二个问题是 Fortran 的 IF 语句。IF 家族的分支语句,在计算机程序设计中可以说必不可少。在 McCarthy 那里 IF 就更重要了,因为几乎所有有递归函数的地方就有 IF(因为递归函数需要 IF 判断结束条件)。相信读者都很熟悉这种 IF 结构

    IF 条件 THEN
        一些语句;
    ELSE
        另一些语句;
    END IF

      这是从 ALGOL 语言一脉相承下来的,很“自然”的 IF 写法。而早期的 FORTRAN 的 IF 写法却不这么直观,而是

    IF (表达式) A B C

      取决于表达式的值是小于零,等于零还是大于零,分别跳到(等价于 goto)标签 A, 标签B 或者标签 C。这个 IF 隐含了三个 Goto,可以说和结构化编程的实践截然相反,降低了程序的可读性。 Fortran 首创的这个三分支跳转的 IF 饱受诟病,Fortran 77 开始支持结构化的 IF,而 Fortran 90 标准进一步宣布三分支跳转的用法已经“过时”,不支持使用。

      在 McCarthy 那里,Fortran 的三分支跳转 IF 也不方便。为此,在 FLPL 中,他试图用一个叫 XIF 子程序完成类似于 IF 的分支功能,用法是:

    XIF(条件, 表达式A, 表达式B)

      取决于条件的满足与否,XIF 返回表达式 A 或者表达式 B 的值。很快,他发现,用子程序的方法实现 XIF,在语义上并不正确。我们知道,在 Fortran 和其他高级语言中,函数参数的值在进入函数之前必须全部确定。在 XIF 这里,不难看出,不管条件满足与否,我们都先要计算表达式 A 和表达式 B 的值。而 IF 是个分支逻辑,从语义上来说,应该只计算满足条件的分支的值。因此,用函数来实现 IF 是不正确的 [读过 SICP 的读者会发现这又是一道习题]。

      作为一个旁注,尽管 John McCarthy 早在50多年前就发现了函数实现 IF 是语义错误的,现代的程序员还常常犯这个错误。一个值得一提的例子是 C++ 逻辑运算符重载和短路表达式的不等价性。我们都知道,在 C 语言中,逻辑与 (&&) 和逻辑或( || ) 都隶属于短路表达式,也就是说,对于 A && B 这样的表达式,如果 A 已经确定为 false,就无需计算表达式 B 的值,即 B 的计算被”短路”。以 C 为蓝本的 C++ 一方便保留了这些短路表达式,另一方面在面向对象的特性中,引入了运算符重载。具体来说,只要一个对象定义了 operator&& 成员函数,就可以进行 && 运算。乍一看,这是一个很酷的特性,可以让程序员用 A&&B 这样的数学表达式表达复杂的逻辑关系。然而,仔细想想,  A.operator&&(B) 在语义上并不等价于 C 所定义的 A&&B,原因在于 A.operator&&() 是个函数,在求值之前需要先计算 B 的值,而后者是个短路表达式,本质上相当于

    IF A:
        return True
    ELSE:
        return B

      因为短路表达式不一定会对 B 求值,这两者从语义上就是不等价的。如果 B 不是一个简单的对象,而是一个复杂表达式的时候,对 B 求值可能有副作用,而这个副作用,是写 A && B 并把它当做短路表达式的程序员所没有预见的。按照 C++ Gotcha 的说法,这很容易造成潜在的程序 Bug。实际上,C++逻辑运算符重载是一个危险的特性,很多公司的编程标准都禁止使用逻辑运算符重载。

      递归和 IF 两个问题,使得 Fortran 从本质上就不符合 McCarthy 期望,以 Fortran 为宿主的开发列表处理语言也不可能达到 McCarthy 想要的结果。因此,唯一的解,就是抛开 Fortran,从头开始,设计一个新的语言。值得注意的是,此时 McCarthy 正好跳槽到了 MIT 做助理教授,MIT 有足够多的编程强人,可以帮 McCarthy 完成这个编写新语言的任务。这个强人,就是 Steve Russell;这个语言,就是 Lisp。

    展开全文
  • 有研究表明,肢体语言在人们交谈时对拉近彼此的距离起着很重要的作用。唐.加博尔博士提出了“软化(SOFTEN)”规则,它能使你更容易接受别人和被别人接受。 S – Smile 微笑。爱笑的人,往往运气不会太差。 O – ...

    “宅”、“木讷”、“难以沟通”往往是人们给程序员贴上的标签,那么本文就向程序员们谈一谈如何能更好地和陌生人交谈。

    (一)肢体动作

    有研究表明,肢体语言在人们交谈时对拉近彼此的距离起着很重要的作用。唐.加博尔博士提出了“软化(SOFTEN)”规则,它能使你更容易接受别人和被别人接受。

    • S – Smile 微笑。爱笑的人,往往运气不会太差。
    • O – Open Arms 张开双臂。双臂交叉或者抱在胸前,是拒绝别人的一种暗示。张开双臂则表示欢迎你的到来。
    • F – Forward 身子前倾。多年前参加面试技巧培训的时候,老师就讲过,虽然向后靠在椅子或沙发上很舒服,但是你传达给面试官的信号是你对你们之间的谈话不是很感兴趣。身子前倾表示你正在努力倾听,而且是对对方的一种恭维
    • T – Touch 接触。初次见面时,最容易被接受的是一个热情的握手,所以不管是工作还是社交场合,主动的伸出你的手吧。
    • E – Eye Contact 眼神交流。经常看到有人在交谈时从不看对方的眼睛,其实适当的眼神接触并给以适时的微笑,表示你很愿意和对方交流。
    • N – Nod 点头。点头表明你正在听对方的谈话,而且理解他说的内容,这是对对方继续讲下去的一种鼓励。

    (二)开始交谈的四个步骤

    和陌生人交谈其实是有规律可循的,基本可以按照下面的四个步骤来进行。

    1. 大胆面对拒绝,首先打招呼。首先打招呼并没有那么恐怖,失去的很少,而得到的会很多。即便被人拒绝了,也不是你的错,他可能心情不好,或者正在忙,再说被陌生人拒绝一次又有什么大不了的呢。
    2. 问一些容易回答的礼节性问题。可以先用一些封闭式的问题(只需回答是或者不是的问题)来试探对方感兴趣的话题,找到以后,可以问一些比较容易回答的开放式问题。
    3. 通过倾听了解你该谈些什么(主动倾听)。问了开放式的问题后,就要开始注意倾听对方的回答了。
      • 不要思考,注意听关键词:很多人在听的时候,在想自己接下来该说什么,那么你很可能会错过对方讲话的一些重点,其实你只需要仔细倾听,抓住关键词,你自然会知道接下来该说什么了。
      • 注意“冰山”式的陈述:所谓“冰山”式的陈述,就是说他的话虽然表面只露出一角,但是水面下其实潜伏着很多的内容。比如他说“你知道接下来发生什么了吗?”,那么他一定接下来想要讲一段很精彩的故事,这时你一定要很好奇的问他:“发生了什么!”
      • 好的倾听者需要练习和专心
    4. 自我展示。只听不说,那你只是一个好的倾听者,而不是一个好的谈话对象。除非比如对方失恋了,对方只需要你作为一个倾诉的对象,否则谈话、聊天应该是一个双向的过程。当别人询问到你感兴趣的话题时,你应该充分介绍自己对这方面了解的情况。

    (三)如何自然而轻松的说下去

    你是否经常苦恼于和陌生人无话可说呢?愉快的交谈应该是听与说的平衡,而达到这种平衡的关键因素就是你们谈论的内容是你们都感兴趣的话题。下面介绍一些小技巧来帮助你找到并且让对方知道双方兴趣的共同点。

    • 从周围的环境入手。差劲的聊天者总是关注自己,考虑自己看起来如何,是否会被人喜欢等等。好的聊天者会观察周围的环境,发现有趣的话题。
    • 对方将大量的时间、金钱放在什么地方,那个地方就是他的兴趣。
    • 在参加某个社交活动前,写下几个你愿意与他人分享的话题。
    • 当表达你的观点时,你可能会听到自己说一些你原来没有说过的话,对多数人来说,这是第一次明确地表达该想法和形成有序概念的时候。交谈也需要练习,多说,下一次你再和别人表达这一观点时,你会说得更好。
    • 快速地插话是对交谈对象的及时反馈,以这种方式让他知道你关心这个话题。
    • 当你发现你们之间有共同点时,马上告诉对方,这样会营造出一种熟悉的感觉,并且还可以让对方知道你对这个话题感兴趣。
    • 注意对方的反应,当对方对你的话题反应冷淡时,应该是时候转换一个话题了。

    (四)选择合适的时机结束交谈

    选择合适的时机结束交谈非常重要,如果你等的太久,你和你的搭档都会感到紧张,变得不自在、焦虑、甚至厌烦。当你觉得是时候结束交谈了,你就要寻找合适的时机。在交谈中会有一些自然的停顿,认真等待这些合适的时刻,结束交谈。

    结束时可以按照下面的四步:

    1. 过渡,可以是一个简单的总结,或者重述对方刚才讲的内容。

    2. 说你和他聊天很愉快。

    3. 有空时我们再聊。

    4. 称呼他人姓名说再见。

    (五)和四种人的交谈方式

    大致有四种人或者叫四种交谈方式:

     

    1. 多言:多言的人侃侃而谈,他们渴望被关注。

    要做的事:

    • 要给他们舞台,赞许他们,欣赏他们
    • 要和他们分享你的兴趣,不然他们会喋喋不休
    • 要大声说话,否则你永远插不上嘴
    • 要显示你的幽默

    不要做的事:

    • 不要纠缠于某个话题的细节
    • 不要聊太沉重的话题,因为玩的开心是他们交谈的首要目的

    2. 率直:率直的人也很多言,也渴望被关注,但有一点不同,他们很喜欢与人争辩。

    要做的事:

    • 要给他们舞台,赞许他们,欣赏他们
    • 要显示你不出风头的幽默

    不要做的事:

    • 不要纠缠于某个话题的细节
    • 不要和他们争辩
    • 听到反驳不要生气

    3. 准确:这种人说话严谨,喜欢就一个话题深入讨论,做技术的人多数讲话时这一种风格。

    要做的事

    • 要赞扬他们的技术知识
    • 要鼓励他们谈论一下专业外的知识

    不要做的事:

    • 不要频繁地转变话题,因为他们喜欢就一个问题深入讨论
    • 不要和他们争辩
    • 听到批评或者建议不要生气

    4. 内敛:内敛的人很敏感,需要额外的鼓励来进行谈话

    要做的事

    • 要表明你对和他们谈话的兴趣
    • 要询问他们的观点和感受,这样才会打开他们的话匣

    不要做的事:

    • 不要争论或者批评他们
    • 不要打断他们或者补全他们的句子
    • 不要放弃,和他们交谈要多花点时间

    其实不论哪种类型,都有一些共同点,比如尽量不要和对方争论或者指责对方,多赞美、欣赏对方。另外每种类型也有自己的特点,比如和多言的人交谈要多分享自己的东西,和准确的人最好就一个话题深入探讨一下,和内敛的人要多鼓励、多询问对方的意见和感受等等。

    (六)总结

    本文介绍了在和陌生人交谈时,应该怎样使用肢体语言,交谈时的四个基本步骤,自然轻松地交谈的核心是听与说的平衡,而其中的关键在于谈论双方都感兴趣的话题,选择合适的时机果断结束交谈,以及和四种不同类型的人的交谈方法。最后,建议参看唐.加博尔的《5分钟和陌生人成为朋友》。













    本文转自JF Zhu博客园博客,原文链接:http://www.cnblogs.com/jfzhu/p/4298427.html    ,如需转载请自行联系原作者



    展开全文
  • 本文带来对自然语言对话未来发展机遇的观点分享。 任何卖不出去的东西,我不会去发明。产品的销售是有用的证明,有用才是成功。(Anything that won’t sell, I don’t want to invent. Its sale is proof of ...
  • 什么是.Net的核心技术?

    千次阅读 2006-09-27 17:10:00
    .NET架构的核心技术■松散联接 跨越网络的分布应用程序逻辑的概念并不是一个新名词,但跨越Web的分布和集成应用程序逻辑的概念却是。 此前,像微软的DCOM (Distributed Component Object Model )、Object ...
  • 目标管理是项目管理的核心思想之

    千次阅读 2015-05-23 17:10:44
    项目干系人目标不一致是导致绝大多数项目失败的主要原因。...任何项目的目标都是由项目的一个或几个干系人因为某些原因提出来的,由于每个人的知识范围、语言、文化、性格等存在差异,导致每个人对项目目标的理解和
  • 【Arduino】一天入门Arduino语言 教程

    万次阅读 多人点赞 2020-04-05 10:19:19
    一天入门Arduino语言 教程 为什么要写这个文章 为了让有一定编程基础和开发经验的同学能够快速的上手 Arduino 同时Arduino 新手也可以借此巩固下知识 如果你有过类似 51 STM32 NXP 等开发经验,根据本篇文章,可以...
  • 编程语言的几点感悟    初学语言,总让人们觉得很有迷惑、疑惑、迷茫,然而对于初学者来说,最先要知道的不是编程语言的使用,而是知道有那些语言可以编辑软件,他们的名字是什么,来源于哪里,甚至是优缺点更是...
  • 2020徐涛核心考案 我们为提升职业生涯做了很多工作。 我们学习新的编程语言; 我们承担新的工作项目; 我们在周末进行辅助项目; 我们为开源社区做出了贡献。 如果我要告诉您,尽管这些活动很有帮助,但是如果您确实...
  • ,而且在诸多高级语言中C是最适合游戏编程的,所以这套软件中的例程及讲解都是基于 VC的。相信,用户通过这套软件的学习将能有信心编出使自己满意的游戏来。 自己满意的游戏是否是大部分人都喜欢的呢?是否能成为市场...
  • 身体语言密码

    万次阅读 2009-08-11 02:12:00
    本书来自www.bookdown.com.cn免费txt小说下载站 更多更新免费电子书请关注www.bookdown.com.cn   掀起职场白领流行新风尚:身体语言密码(全文) 一个...是的,那些被我们所忽略的微小的身体语言,就是有着如此之大
  • 高级编程语言的发展历程

    千次阅读 2012-02-11 16:08:24
     高级编程语言的发展历程(三)FORTRAN 语言是怎么来的  高级编程语言的发展历程(四)LISP 和 AI 的青梅竹马 A  高级编程语言的发展历程(五)LISP 和 AI 的青梅竹马 B  高级编程语言的发展历程(六)SCHEME...
  • 当你是从百度或google搜索 关键词"ASP.NET核心技术",".NET核心技术"过来的话,我很想鄙视你一下,真的,因为我遇到太多的这样的人了,"ASP.NET核心技术",".NET核心技术"这样的问题真的没必要问,有意思吗,基础弄懂了吗...
  • 在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令...
  • Concurnas是一种新的开源JVM编程语言,旨在用于构建并发和分布式系统。Concurnas是一种静态类型的语言,具有面向对象,功能和反应式编程构造。 凭借隐藏多线程复杂性的简洁语法以及对GPU计算,向量化和矩阵等数据...
  • 金尼 Djinni是用于生成跨语言类型声明和接口绑定的工具。 它旨在将C ++与Java或Objective-C连接起来。... 支持三种核心语言的原始类型和用户定义的枚举,记录和接口的交集。 生成接口代码,该接口代码允许在
  • 微软架构师谈编程语言发展 分类: 计算机语言 2011-09-10 14:37 60人阅读 评论(0) 收藏 举报   微软架构师谈编程语言发展(一) 2008年04月18日 星期五 下午 03:28 本文是对微软...
  • 作者 |Jakub Lukasiewicz译者 |弯月来源 | CSDN(ID:CSDNnews)以下为译文:“第一门编程语言学C靠谱吗?”“C还有未来吗?”“我应该考虑学C吗?...
  • java语言基础

    千次阅读 2013-03-20 09:23:42
    java语言基础 谈到Java语言基础学习的书籍,大家肯定会推荐Bruce Eckel的《Thinking in Java》。它是一本写的相当深刻的技术书籍,Java语言基础部分基本没有其它任何一本书可以超越它。该书的作者Bruce Eckel在...
  • lua语言简介
  • 高级编程语言的发展

    2015-05-31 17:47:22
     高级编程语言的发展历程(三)FORTRAN 语言是怎么来的  高级编程语言的发展历程(四)LISP 和 AI 的青梅竹马 A  高级编程语言的发展历程(五)LISP 和 AI 的青梅竹马 B  高级编程语言的发展历程(六)SCHEME ...
  • t/JSP编程本质就是在反复调用这些类来通过HTTP协议在Web Server和Brower之间交谈。另 外对JSP,还需要熟悉几个常用JSP的标记,具体的写法记不住的话,临时查就是了。  此外Java Web编程学习的重点要放在Web ...
  • 比如我们两个人在公司交谈时的上下文是一个上下文,但是在路上交谈时,则切换到另一个上下文了,因为交谈的地点发生了变化;所以,BC合起来理解,就是一个上下文的边界。 · BC有什么用呢? 就是为了表达上面介绍的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,824
精华内容 4,729
关键字:

交谈的核心是语言