Rai*_*wig 41
当然,问题是宏是否方便使用以及它们有多强大.
让我们先来看看Lisp是如何略有不同的.
Lisp语法基于数据,而不是文本
Lisp有两阶段语法.
A)首先是s表达式的数据语法
例子:
(mary called tim to tell him the price of the book)
(sin ( x ) + cos ( x ))
Run Code Online (Sandbox Code Playgroud)
s表达式是原子,原子列表或列表.
B)第二,在s表达式之上有Lisp语言语法.并非每个s表达式都是有效的Lisp程序.
(3 + 4)
Run Code Online (Sandbox Code Playgroud)
不是一个有效的Lisp程序,因为Lisp使用前缀表示法.
(+ 3 4)
Run Code Online (Sandbox Code Playgroud)
是一个有效的Lisp程序.第一个元素是一个函数 - 这里是函数+.
S表达式是数据
有趣的是,现在可以读取s表达式,然后Lisp使用普通的数据结构(数字,符号,列表,字符串)来表示它们.
大多数其他编程语言没有内化源的原始表示 - 除了字符串.
请注意,此处的s表达式不表示AST(抽象语法树).它更像是一个来自词法分析阶段的分层令牌树.词法分析器识别词汇元素.
内置的源代码现在可以很容易地使用代码进行计算,因为可以应用操作列表的常用函数.
使用列表函数的简单代码操作
让我们看一下无效的Lisp代码:
(3 + 4)
Run Code Online (Sandbox Code Playgroud)
该程序
(defun convert (code)
(list (second code) (first code) (third code)))
(convert '(3 + 4)) -> (+ 3 4)
Run Code Online (Sandbox Code Playgroud)
已将中缀表达式转换为有效的Lisp前缀表达式.我们可以对它进行评估.
(eval (convert '(3 + 4))) -> 7
Run Code Online (Sandbox Code Playgroud)
EVAL评估转换后的源代码.eval将s表达式作为输入,这里是一个列表(+ 3 4).
如何用代码计算?
现在,编程语言至少有三种选择可以使源计算成为可能:
基于字符串转换的源代码转换
使用类似于Lisp的原始数据结构.更复杂的变体是基于XML的语法.然后可以转换XML表达式.还有其他可能的外部格式与内部化数据相结合.
使用真实的语法描述格式,并使用表示语法类别的数据结构表示内化为语法树的源代码. - >使用AST.
对于所有这些方法,您将找到编程语言.Lisp或多或少都在阵营2中.结果:它在理论上并不真正令人满意,并且无法静态解析源代码(如果代码转换基于任意Lisp函数).Lisp社区几十年来一直在努力解决这个问题(例如,参见Scheme社区尝试过的无数方法).幸运的是,与一些替代方案相比,它相对容易使用并且相当强大.变体1不太优雅.变体3在简单和复杂的转换中导致很多复杂性.它通常也意味着表达式已经针对特定的语言语法进行了解析.
另一个问题是如何转换代码.一种方法将基于转换规则(如在一些Scheme宏变体中).另一种方法是特殊的转换语言(比如可以进行任意计算的模板语言).Lisp方法是使用Lisp本身.这使得使用完整的Lisp语言编写任意转换成为可能.在Lisp中没有单独的解析阶段,但在任何时候都可以读取,转换和评估表达式 - 因为这些函数可供用户使用.
Lisp是代码转换的本地最大简单性.
其他前端语法
另请注意,该函数read将s表达式读取到内部数据.在Lisp中,可以使用不同的读取器来获得不同的外部语法,或者重用Lisp内置读取器并使用读取宏机制对其进行重新编程 - 这种机制可以扩展或更改s表达式语法.两种方法都有一些示例可以在Lisp中提供不同的外部语法.
例如,有一些Lisp变体具有更传统的语法,其中代码被解析为s表达式.
为什么基于s表达式的语法在Lisp程序员中很流行?
当前的Lisp语法在Lisp程序员中很流行,原因有两个:
1)数据是代码是数据的想法使得基于内化数据编写各种代码转换变得容易.从阅读代码,操作代码到打印代码,还有一种相对直接的方法.可以使用通常的开发工具.
2)文本编辑器可以直接编程来操作s表达式.这使得编辑器中的基本代码和数据转换相对容易.
最初Lisp被认为具有不同的,更传统的语法.之后有几次尝试切换到其他语法变体 - 但由于某些原因,它要么失败,要么产生不同的语言.
Ken*_*Ken 20
绝对.如果你必须处理复杂的语法,它只需要几个数量级的复杂程度.正如Peter Norvig所说:
Python确实可以访问程序的抽象语法树,但这不适合胆小的人.从好的方面来说,这些模块很容易理解,通过5分钟和5行代码,我得到了这个:
>>> parse("2 + 2")
['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison',['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term',['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor',['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]这对我来说是一种失望.等价表达式的Lisp解析是
(+ 2 2).似乎只有真正的专家才能操纵Python解析树,而Lisp解析树对任何人来说都很简单.通过连接字符串仍然可以在Python中创建类似于宏的东西,但它没有与语言的其余部分集成,因此在实践中没有完成.
因为我不是超级天才(甚至是Peter Norvig),所以我会坚持下去(+ 2 2).
Xan*_*hir 13
这是Rainer答案的缩短版本:
为了拥有lisp风格的宏,您需要一种在数据结构中表示源代码的方法.在大多数语言中,唯一的"源代码数据结构"是一个字符串,它不具有几乎足够的结构,让你做真实的宏.有些语言提供了真正的数据结构,但它太复杂了,就像Python一样,所以编写真正的宏是非常复杂的,并不值得.
Lisp的名单和括号在中间达到了最佳位置.足够的结构使其易于处理,但不是太多,所以你淹没了复杂性.作为奖励,当您嵌套列表时,您会得到一棵树,这恰好是编程语言自然采用的结构(在实际解释/编译之前,几乎所有编程语言都首先被解析为"抽象语法树"或AST) ).
基本上,编程Lisp是直接编写AST,而不是编写一些其他语言,然后由计算机转换为AST.您可能会放弃这些问题,但您只需要一些其他方法将事物分组到列表/树中.这样做你可能不会获得太多收益.
括号与宏无关.这只是Lisp的做事方式.
例如,Prolog有一个非常强大的宏机制称为"术语扩展".基本上,每当Prolog读取术语T时,如果尝试特殊规则term_expansion(T, R).如果成功,则解释R的内容而不是T.
是.Lisp中的括号以经典方式使用,作为分组机制.缩进是表达群体的另一种方式.例如,以下结构是等效的:
A ((B C) D)
Run Code Online (Sandbox Code Playgroud)
和
A
B
C
D
Run Code Online (Sandbox Code Playgroud)