Racket 友好入门:一门能造语言的 Lisp
“Lisp值得学习,因为当你最终掌握它时,将获得深刻的启迪体验。”——埃里克·S·雷蒙德
欢迎。今天你将学习一种来自编程史上最古老且最独特的语言家族之一。一种代码是数据,括号是纯结构,程序可以写程序的语言。完成这个教程时,你就能写出自己的语法。
一点历史
Lisp 出生于1958由麻省理工学院的约翰·麦卡锡发明。作为背景说明:它是仍在使用的第二古老的高级语言(只有1957年的Fortran比它早一年)。
Python于1991年加入。1995年启用JavaScript。Lisp比他们早了30多年,而我们现在认为“现代”的几个想法就是在那里诞生的:
- 垃圾收集—— 发明对于口齿不清。
- 一类函数—— 通过函数作为参数传递,现已成为所有地方的标准。
- The REPL—— Python、Node 和 Julia 今天都在 Lisp 中开始的交互式读-评估-打印循环。
- 条件句作为表达式——
if返回一个值。 - 同质性—— 代码是语言本身的数据结构。这是最重要的。我们最后再回来谈。
几十年来,Lisp 一直存在该人工智能的语言。在70年代和80年代,曾有物理计算机设计用于直接运行Lisp::Lisp 机器由Symbolics和LMI共同制造。
随后到来了“人工智能冬天”,资金枯竭,Lisp从明星变成了邪典语言。
但有趣的想法不会消失,它们会变异,这正是口齿不清的魅力所在。
从Lisp到Scheme再到Racket
1975年,杰拉尔德·萨斯曼和盖伊·斯蒂尔创立了方案:一种极简、优雅、几乎数学化的Lisp。Scheme 成为学术界最喜爱的语言教学编程(传奇之书)SICP《计算机程序的结构与解释》用Scheme编写)。
1995年,Matthias Felleisen的乐团成立了PLT计划,一个旨在教育和编程语言研究的方案。2010年更名敲诈如今,它远不止是一个方案:它是一种用于构建语言的语言。
其非官方座右铭是面向语言的编程如果你的问题需要独立的语言,Racket让你在一个下午内就能建立一个。
| 年份 | 事件 |
|---|---|
| 1958 | 麦卡锡在麻省理工发明了Lisp |
| 1975 | 萨斯曼和斯蒂尔创造了Scheme |
| 1984 | 通用Lisp是标准化的 |
| 1995 | PLT计划(现为Racket)诞生 |
| 2007 | Clojure诞生(JVM上的Lisp) |
| 2010 | PLT计划更名为Racket |
| 现今 | 你,正在读这段话,准备写括号 UwU |
谁用Lisp现今?
比你想象的还要多:
- 克洛尤尔在银行、航空公司和初创企业中运行(拉丁美洲最大的数字银行Nubank运行于Clojure上)。
- 常见口齿不清(使用SBCL编译器)仍然活跃于专家系统、飞行计划(ITA软件,被谷歌收购,支持谷歌飞行)和科学计算领域。
- Emacs Lisp——每天有数百万人在不知情的情况下运行Lisp,因为他们的编辑器是一个Lisp翻译器。
- 狡诈/粉笔——一个100%配置在Scheme中的整个Linux发行版。
- 敲诈拥有自己的年度会议(RacketCon),一个活跃的学术和艺术社区,并被用于语言研究、形式验证(Rosette)、排版与出版(Pollen)以及全球范围内的教育。
- 而且新的Lisp不断出现:茴香(一种编译成Lua的Lisp,游戏中很受欢迎),珍妮特,也就是说,(Python上的Lisp)......
给TADC粉丝的彩蛋:
在惊奇数字马戏团(第8集,“hjsakldfhl”),当金格打开终端试图重置凯恩时,你可以看到凯恩(1996年建造的创造性人工智能)被编程为Lisp。文件字面上的名字就是Caine-core.lisp.安装(5分钟)
- 前往https://racket-lang.org
- 下载适用于你系统(Linux、macOS、Windows)的安装程序。
- 开门DrRacket,包括随之而来的环境。
DrRacket有两个区域:顶部你写下你的定义(你的程序),底部有REPL为了现场实验。在定义区域的第一行,写下:
#lang racket
这句话告诉Racket你用的是哪种语言(记住:Racket是语言工厂,你得选一种)。
如果你更喜欢终端:racket指令会给你一个REPL,并且raco是包管理器和工具命令。
第一次接触:一切都是一种表达
在REPL中,试试:
> (+ 1 2) 3 > (* 3 (+ 2 2)) 12 > (string-append "hello " "world") "hello world"
Lisp的规则可以用一句话说:
一切都是(operator argument1 argument2 ...).永远如此。没有例外。没有操作先例需要记忆,也没有特殊的语法。(+ 1 2)补充道。(if ...)决定。(define ...)名字。那些乍看之下令人望而生畏的括号,实际上是完全没有任何任意规则。
一周后,你就不再见到他们了。
定义与功能
#lang racket (define pi-approx 3.14159) (define (circle-area r) (* pi-approx r r)) (circle-area 2) ; => 12.56636
define用名字创造一个常数。define其中(name arguments...)创建一个函数。- 评论开头如下
;.
匿名函数使用lambda(是的,那lambda Church 在 1930 年代的 lambda 演算是这一切的理论祖先):
(lambda (x) (* x x)) ; a function with no name ((lambda (x) (* x x)) 5) ; => 25, applied directly
列表:Lisp的核心
Lisp 代表DSLtP罗斯辛。列表是基本结构:
(list 1 2 3) ; => '(1 2 3) '(1 2 3) ; the same thing, "quoted" (first '(1 2 3)) ; => 1 (rest '(1 2 3)) ; => '(2 3) (cons 0 '(1 2 3)) ; => '(0 1 2 3) (length '(a b c)) ; => 3
注意引号'.它告诉Racket:别评估这些,那是数据.记住这个细节,这是通往最后一幕的门。
高阶函数
这正是《Racket》的亮点。将函数传递给其他函数是世界上最自然的事情:
(map (lambda (x) (* x x)) '(1 2 3 4 5)) ; => '(1 4 9 16 25) (filter even? '(1 2 3 4 5 6)) ; => '(2 4 6) (foldl + 0 '(1 2 3 4 5)) ; => 15
map变换,filter精选,foldl积累。有了这三个函数,你几乎可以在不写任何东西的情况下解决大多数列表问题for循环。
递归:螺旋式思考
在Lisp里,你不会想“重复N次”,而是想“基础情况是什么,我该如何朝那个方向前进?”:
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
(factorial 5) ; => 120
为了让事情更具视觉效果,我们来画点东西。Racket 自带的图形库包括:
#lang racket
(require 2htdp/image)
(define (sierpinski level)
(if (= level 0)
(triangle 8 "solid" "purple")
(let ([t (sierpinski (- level 1))])
(above t (beside t t)))))
(sierpinski 6)
把它粘贴到DrRacket,点击运行,然后看到Sierpinski三角形出现在你的屏幕上。
压轴:写代码的代码
记住引号'它把代码转化为数据。观看:
'(+ 1 2) ; => the LIST (+ 1 2), not the number 3 (first '(+ 1 2)) ; => the symbol + (eval '(+ 1 2)) ; => 3. You just evaluated data as code.
你的程序就是一份清单。你可以建立列表。因此:你可以用程序构建程序。这是同质性这也是Lisp 有 real 的原因宏量营养素不是像 C 语言那样的文本宏,而是在运行前接收代码和返回代码的函数。
Racket没有while循环?我们来发明一个:
(define-syntax-rule (while condition body ...)
(let loop ()
(when condition
body ...
(loop))))
(define counter 0)
(while (< counter 5)
(displayln counter)
(set! counter (+ counter 1)))
你只是延伸语言!在Lisp中,语法由你决定。
艾伦·凯打电话给Lisp“麦克斯韦软件方程”:一个微小的核心,其他一切都可以由此推导出来。