【转】谈语法

谈语法

使用和研究过这么多程序语言之后,我觉得几乎不包含多余功能的语言,只有一个:Scheme。所以我觉得它是学习程序设计最好的入手点和进阶工具。当然 Scheme 也有少数的问题,而且缺少一些我想要的功能,但这些都瑕不掩瑜。在用了很多其它的语言之后,我觉得 Scheme 真的是非常优美的语言。

要想指出 Scheme 所有的优点,并且跟其它语言比较,恐怕要写一本书才讲的清楚。所以在这篇文章里,我只提其中一个最简单,却又几乎被所有人忽视的方面:语法。

其它的 Lisp “方言”也有跟 Scheme 类似的语法(都是基于“S表达式”),所以在这篇(仅限这篇)文章里我所指出的“Scheme 的优点”,其实也可以作用于其它的 Lisp 方言。从现在开始,“Scheme”和“Lisp”这两个词基本上含义相同。

我觉得 Scheme (Lisp) 的基于“S表达式”(S-expression)的语法,是世界上最完美的设计。其实我希望它能更简单一点,但是在现存的语言中,我没有找到第二种能与它比美。也许在读过这篇文章之后,你会发现这种语法设计的合理性,已经接近理论允许的最大值。

为什么我喜欢这样一个“全是括号,前缀表达式”的语言呢?这是出于对语言结构本质的考虑。其实,我觉得语法是完全不应该存在的东西。即使存在,也应该非常的简单。因为语法其实只是对语言的本质结构,“抽象语法树”(abstract syntax tree,AST),的一种编码。一个良好的编码,应该极度简单,不引起歧义,而且应该容易解码。在程序语言里,这个“解码”的过程叫做“语法分析”(parse)。

为什么我们却又需要语法呢?因为受到现有工具(操作系统,文本编辑器)的限制,到目前为止,几乎所有语言的程序都是用字符串的形式存放在文件里的。为了让字符串能够表示“树”这种结构,人们才给程序语言设计了“语法”这种东西。但是人们喜欢耍小聪明,在有了基本的语法之后,他们开始在这上面大做文章,使得简单的问题变得复杂。

Lisp (Scheme 的前身)是世界上第二老的程序语言。最老的是 Fortran。Fortran 的程序,最早的时候都是用打孔机打在卡片上的,所以它其实是几乎没有语法可言的。

显然,这样写程序很痛苦。但是它却比现代的很多语言有一个优点:它没有歧义,没有复杂的 parse 过程。

在 Lisp 诞生的时候,它的设计者们一下子没能想出一种好的语法,所以他们决定干脆先用括号把这语法树的结构全都括起来,一个不漏。等想到更好的语法再换。

自己想一下,如果要表达一颗“树”,最简单的编码方式是什么?就是用括号把每个节点的“数据”和“子节点”都括起来放在一起。Lisp 的设计者们就是这样想的。他们把这种完全用括号括起来的表达式,叫做“S表达式”(S 代表 “symbolic”)。这貌似很“粗糙”的设计,甚至根本谈不上“设计”。奇怪的是,在用过一段时间之后,他们发现自己已经爱上了这个东西,再也不想设计更加复杂的语法。于是S表达式就沿用至今。

在使用过 Scheme,Haskell,ML,和常见的 Java,C,C++,Python,Perl,…… 之后,我也惊讶的发现, Scheme 的语法,不但是最简单,而且是最好看的一个。这不是我情人眼里出西施,而是有一定理论依据的。

首先,把所有的结构都用括号括起来,轻松地避免了别的语言里面可能发生的“歧义”。程序员不再需要记忆任何“运算符优先级”。

其次,把“操作符”全都放在表达式的最前面,使得基本算术操作和函数调用,在语法上发生完美的统一,而且使得程序员可以使用几乎任何符号作为函数名。

在其他的语言里,函数调用看起来像这个样子:f(1),而算术操作看起来是这样:1+2。在 Lisp 里面,函数调用看起来是这样(f 1),而算术操作看起来也是这样(+ 1 2)。你发现有什么共同点吗?那就是 f+ 在位置上的对应。实际上,加法在本质也是一个函数。这样做的好处,不但是突出了加法的这一本质,而且它让人可以用跟定义函数一模一样的方式,来定义“运算符”!这比起 C++ 的“运算符重载”强大很多,却又极其简单。

关于“前缀表达式”与“中缀表达式”,我有一个很独到的见解:我觉得“中缀表达式”其实是一种过时的,来源于传统数学的历史遗留产物。几百年以来,人们都在用 x+y 这样的符号来表示加法。之所以这样写,而不是 (+ x y),是因为在没有计算机以前,数学公式都得写在纸上,写 x+y 显然比 (+ x y) 方便简洁。但是,中缀表达式却是容易出现歧义的。如果你有多个操作符,比如 1+2*3。那么它表示的是 (+ 1 (* 2 3)) 呢,还是 (* (+ 1 2) 3)?所以才出现了“运算符优先级”这种东西。看见没有,S表达式已经在这里显示出它没有歧义的优点。你不需要知道 +* 的优先级,就能明白 (+ 1 (* 2 3))(* (+ 1 2) 3) 的区别。第一个先乘后加,而第二个先加后乘。

对于四则运算,这些优先级还算简单。可是一旦有了更多的操作,就容易出现混淆。这就是为什么数学(以及逻辑学)的书籍难以看懂。 实际上,那些看似复杂的公式,符号,不过是在表示一些程序里的“数据结构”,“对象”以及“函数”。大部分读数学书的时间,其实是浪费在琢磨这些公式:它们到底要表达的什么样一个“数据结构”或者“操作”!这个“琢磨”的过程,其实就是程序语言里所谓的“语法分析”(parse)。

这种问题在微积分里面就更加明显。微积分难学,很大部分原因,就是因为微积分的那些传统的运算符,其实不是很好的设计。如果你想了解更好的设计,可以参考一下 Mathematica 的公式设计。试试在 Mathematica 里面输入“单行”的微积分运算(而不使用它传统的“2D语法”)。

其实 Lisp 已经可以轻松地表示这种公式,比如对 x^2 进行微分,可以表示成

      (D ‘(^ x 2) ‘x)

看到了吗?微分不过是一个用于处理符号的函数 D,输入一个表达式和另一个符号,输出一个新的表达式。

同样的公式,传统的数学符号是这个样子:

这是什么玩意啊?d 除以 dx,然后乘以 x 的平方?

在 Lisp 里,你其实可以比较轻松地实现符号微分的计算。SICP里貌似有一节就是教你写个符号微分程序。做微积分这种无聊的事情,就是应该交给电脑去做。总之,这从一方面显示了,Lisp 的语法其实超越了传统的数学。

其实我一直都在想,如果把数学看成是一种程序语言,它也许就是世界上语法最糟糕的语言。数学里的“变量”,几乎总是没有明确定义的作用域(scope)。也就是说他们只有“全局变量”。上一段话的 x,跟下一段话的 x,经常指的不是同一个东西。所以训练有素的数学家,总是避免使用同一个符号来表示两种不同的东西。很快他们就发现所有的拉丁字母都用光了,于是乎开始用希腊字母。大写的,小写的,粗体的,斜体的,花体的,…… 而其实,他们只不过是想实现 C++ 里的 “namespace”。

可惜的是,很多程序语言的设计者没能摆脱数学的思想束缚,对数学和逻辑有盲目崇拜的倾向。所以他们继续在新的语言里使用中缀表达法。Haskell,ML,Coq,Agda,这些“超高级”的语言设计,其实都中了这个圈套。在 Coq 和 Agda 里面,你不但可以使用中缀表达式,还可以定义所谓的 “mixfix” 表达式。这样其实是把简单的问题复杂化。想让自己看起来像“数学”,很神秘的样子,其实是学会了数学的糟粕,自讨苦吃。

另外,由于 Lisp 的表达能力和灵活性比其他语言要大很多,所以类似 C 或者 Pascal 那样的语法其实不能满足 Lisp 的需要。在 Lisp 里,你可以写 (+ 10 (if test 1 2)) 这样的代码,然而如果你使用 C 那样的无括号语法,就会发现没法很有效的嵌入里面的那个条件语句而不出现歧义。这就是为什么 C 必须使用 test? 1 : 2 这样的语法来表示 Lisp 的 if 能表示的东西。然而即使如此,你仍然会经常被迫加上一对括号,结果让程序非常难看,最后的效果其实还不如用 Lisp 的语法。在 C 这样的语言里,由于结构上有很多限制,所以才觉得那样的语法还可以。可是一旦加入 Lisp 的那些表达能力强的结构,就发现越来越难看。JavaScript(node.js)就是对此最好的一个证据。

最后,从美学的角度上讲,S表达式是很美观的设计。所有的符号都用括号括起来,这形成一种“流线型”的轮廓。而且由于可以自由的换行排版,你可以轻松地对齐相关的部分。在 Haskell 里,你经常会发现一些很蹩脚,很难看的地方。这是因为中缀表达式的“操作符”,经常不能对在一起。比如,如果你有像这样一个 case 表达式:

case x
  Short _ -> 1
  VeryLooooooooooooooooooooooooog _ -> 2

为了美观,很多 Haskell 程序员喜欢把那两个箭头对齐。结果就成了这样:

case x
  Short _                           -> 1
  VeryLooooooooooooooooooooooooog _ -> 2

作为一个菜鸟级摄影师,你不觉得第一行中间太“空”了一点吗?

再来看看S表达式如何表达这东西:

(case x
  (-> (Short _) 1)
  (-> (VeryLooooooooooooooooooooooooog _) 2))

发现“操作符总在最前”的好处了吗?不但容易看清楚,而且容易对齐,而且没有多余的间隙。

其实我们还可以更进一步。因为箭头的两边全都用括号括起来了,所以其实我们并不需要那两个箭头就能区分“左”和“右”。所以我们可以把它简化为:

(case x
  ((Short _) 1)
  ((VeryLooooooooooooooooooooooooog _) 2))

最后我们发现,这个表达式“进化”成了 Lisp 的 case 表达式。

Lisp 的很多其它的设计,比如“垃圾回收”,后来被很多现代语言(比如 Java)所借鉴。可是人们遗漏了一个很重要的东西:Lisp 的语法,其实才是世界上最好的语法。

原文地址:https://www.cnblogs.com/alantu2018/p/8496289.html

时间: 2024-10-11 01:23:57

【转】谈语法的相关文章

我的技术我做主,将Oracle进行到底!

本人接触Oracle是在2005年,至今已有10年了,从一个门外汉走到今天,成为一个刚进门的门外汉,一路走来的酸甜苦辣无以言表.今天不谈语法.不谈语义.不谈计划,也不谈执行,更不谈技术了,先说说感受. 先说酸甜苦辣吧,2006年在深圳培训时,天天学习Oracle到晚上9点,一周一小考,一月一大考,苦啊! 2007年刚到佛山移动没多久,就在生产环境搞出了一个故障,吓的出了一身冷汗,手都在发抖,辣啊! 技术不精,知识不到位,受人嘲笑和指责,酸啊! 手到病除,救人于危难之中,甜啊! 今天主要谈下面两个

信安周报-第02周:SQL基础

信安之路 第02周 Code:https://github.com/lotapp/BaseCode/tree/master/safe 前言 本周需要自行研究学习的任务贴一下: 1.概念(推荐) 数据库系列去年就开始陆陆续续的发文,这周任务简单带过,概念部分我更新了一下,其他部分看扩展吧~ 1.1.关系型数据库 引用百科的一段抽象描述: "关系型数据库,是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库.用户通过

浅谈Kotlin(二):基本类型、基本语法、代码风格

浅谈Kotlin(一):简介及Android Studio中配置 通过上面的文章,在Android Studio中我们已经可以进行Kotlin编程了,接下来开始学习Kotlin的基本类型及语法. 一.基本类型 在 Kotlin 中,所有变量的成员方法和属性都是一个对象. 一些类型是内建的,它们的实现是优化过的,但对用户来说它们就像普通的类一样.   注意,第一个字母大写,Kotlin 区分大小写 主要是以下几种字面值常量: --数型: 123 --长整型要加大写 L : 123L --16进制:

浅谈MVC Razor基本语法

Razor语法是在MVC3.0引入的全新的c#语法,取而代之<%...%>语法.用在mvc的view页面. 首先谈一下razor语法的基本用法: 1.如果在页面输出单一变量时,只要在c#语句之前加上@符号即可,范例如下: <p> 现在时刻:@DateTime.Now </p> 2.在页面中输出一段含有空白子元或运算子的结果时,必须在前后加上一个小括弧,范例如下: <p> 会员名称:@(User.Identity.Name+Model.MemberLevel)

浅谈HTML基本语法

什么是 HTML? HTML 是用来描述网页的一种语言.HTML 指的是超文本标记语言 (Hyper Text Markup Language)HTML 不是一种编程语言,而是一种标记语言 (markup language)标记语言是一套标记标签 (markup tag)HTML 使用标记标签来描述网页 HTML 标签 HTML 标记标签通常被称为 HTML 标签 (HTML tag).HTML 标签是由尖括号包围的关键词,比如 <html>HTML 标签通常是成对出现的,比如 <b&g

浅谈javascript中的For in语法

相信大家都使用过javascript中的for循环,主要用来遍历数组对象,方便执行重复操作,体现代码的重用性.但是,应为数组的索引一般是整 型的数字,当遇到JSON对象或者object对象时,就不能使用for循环遍历了,应当使用for in函数遍历对象,这里就谈谈个人对for in的理解. 首先,虽然叫For in语法但关键字还是用for,这个语法还可以用来遍历对象,拿到的是对象的属性名称,然后通过对象名[属性名称]就可以拿到对象.个人觉得,理解这个语法的本质,关键在于理解每次循环得到的到底是什

运维工具Ansible浅谈playbook讲解以及YAML语法和JSON语法的互化

引言:运维发展到今天已经远远不是传统的运维做一些重复性的枯燥工作,面对海量爆发的访问量,传统的运维已经很吃力,比如让你装三五台机器的系统,这个so easy,那要是安装几百上千台呢,还easy吗.我要安装nginx服务,并提供好相应的端口转发机制以及location资源访问机制,但是有多发十几台这样的机制,并且每台机器转发机制都不相同,这个对我们来说是一个不小的挑战,因此运维进入了自动化时代,自动化运维就显得重要了.因此本文就是围绕自动化运维工具Ansible来展开的. 一,为什么要使用Ansi

MVC4相关Razor语法浅谈

1._LayOut.cshtml 文件为mvc的布局文件,里面包函的是htm的静态文件,作为mvc其他view的基础母版使用,子视图要不想想调用它只需在页面设置@{Layout=null;}即可,现对于里面的一些语法进行说明: @RenderBody()对于所有的页面默认的情况下都会使用这个布局(WebForm的模板),在页面只能调用一次,子view可以与其共享资源如js.css等: @RenderPage("url")相当于一个占位符其页面的所有内容都会被引擎渲染在这个地方.比如网页

c#语法复习总结(1)-浅谈c#.net

出来工作两年,发现自己进步太小了,工作能力是不能混的,想先从基础知识好好复习一下,再深入的学习一些高级框架和先进的理念.找回了博客园的密码账号,好好学习和总结.先从数据类型总结一下,无非就是值类型,引用类型,在菜鸟教程上看到一种指针类型,这个之前没学习到之后总结一下.说明一下,个人总结方便查看,快速的话推荐看菜鸟教程,实体书效果更好,我自己买了一本厚厚的C#... c# .net关系,c#是语言,.net是框架.大体是这样,有时候面试题也会这样问c# .net关系,这样回答在百度上也可以百度到.