作者:何岩,recreating.org出品,谢绝转载。
阅读SICP,站在Lisp发明者的上帝视角来思考:“我为什么要这么设计Lisp?”
0.前言
本系列为Recreating Lisp的准备阶段。因为,硬核知识要基于SICP,所以,我需要一边读SICP一边构建“纯”定位体系。
What - 本系列写的是什么?
定位,即,定位出高级编程语言(本系列以Lisp编程语言为代表)的概念体系。
Why - 为什么要写定位?
因为,SICP的定位感不好,多是结论,少有原因。所以读起来容易迷茫。我想解决这个问题。(其实SICP有定位,只不过一两句带过,很难让人当回事)
How - 我如何写?
我要用直觉和想象力,填补SICP缺失的“为什么”。
我要将SICP中的珍珠(结论)用线(问题)串起来。
外面再裹一层糖衣(故事)。
最终呈现一个概念体系,体系的核心是Lisp编程语言。
所以,我要站在Lisp发明人的上帝视角不断的提问:“我为什么要设计这个概念?(Why I design this concept?)”
1.Why - 我为什么要创造Lisp这个高级编程语言?
为了构建抽象。
Why - 为什么构建抽象?
为了模拟真实世界。
Why - 为什么要模拟真实世界?
加速/预测世界的运行。(没想到的是,人们逐渐喜欢生活在虚拟世界中了)
2.Why - MIT的两位教授为什么要写SICP?
为了让读者得到高级编程语言(以Lisp为代表)的本质:构建和解释。
Part1:构建(Structure),讲述了Lisp编程语言应该如何设计,才能满足这样一个核心业务,即,构建抽象(Build Abstraction)。
构建抽象的核心挑战则是:控制复杂度(Control Complexity)
Part2:解释(Interpretation),讲述了在底层如何实现Lisp编程语言的存在,即,解释器(Interpreter)。
从不同的角度看,解释器分为软解释器和硬解释器。
软解释器就是Meta-Circular Evaluator,
硬解释器就是Lisp Machine(用硬件来实现Meta-Circular Evaluator)。
解释器的核心挑战就是,理解漩涡的内环:Eval/Apply
3.What - 为了构建抽象世界,需要什么样的材料?
函数就是抽象世界中的原子。而且有且只需要函数这唯一的类型。
这就像现实世界,从构建世界的材料视角来看,最小可组装单元,只有原子。分子由原子组成。量子/夸克无法自由组装,他们是原子的组成成分。
所以,函数就是原子。
(读了第二章之后,我觉得函数不是原子,Data才是原子。函数应该是夸克,是非物质的存在,是信息。而Data是被函数模拟出来的假象,是我们通过感受函数运转表现出来的色,所自动产生的假象体。所以说Data我们认为是物质实体,本质上则是空的,所以说,色即是空)
Lambda演算正是在表达这个思想:
1.Lambda表达式就是函数,任何其他元素都可以用Lambda表达式来构建。包括数字(丘奇数)和加法。
2.Lambda的律则,就像宇宙最初的物理规律一样,短短几条而已。
4.Why - Lisp为什么选择Lambda作为内核?
1.Lambda的维函数观念是对的,适合构建抽象。
2.Lisp要做的本质就是以Lambda演算为核心,填补Lambda演算的不足。
3.如果想在计算机里运行Lambda演算,需要添加一些东西,例如环境(Enviroment)。
5.What - Lisp与Lambda演算有哪些不同?能否画出一个漩涡关系图来?
1.漩涡的内核是Lambda
2.漩涡的外层开始是List数据结构
3.基于List数据结构,产生了可以保存状态的环境(Environment)
4.函数reduce的形式有所不同等等。
5.所以定位呈现为,以Lambda演算为核心的辐射。需要我再去深入的理解Lambda演算。
6.Lisp与Lambda演算的关系
Lisp中的expression的evaluate就是Lambda演算的beta规约(β-reduction)。
求值的本质就是组装,变形。这也是计算的本质。
7.What - Lambda的本质是什么?
λ演算是一个为了表达和计算函数的形式化系统,有着自己的化简规则和语法。整个系统是基于表达式的(也叫λ项)。
expression = variable || abstraction || application (也就是说这三种表达式都叫Lambda项)
1.组成-3条律则
- variable变量 —> primitive expressions
- abstraction抽象体 —> means of abstraction
- application应用 —> means of combination
2.运行-3条规约 - α-conversion
- β-reduction
- η-reduction
8.图灵机和Lambda演算的关系是什么?
不管是图灵机还是 lambda 演算都是为了解决可计算问题,也就是说图灵完备仅仅是 “可以计算出一个值”而不涉及其他的。函数式中将超过计算的东西叫做副作用,因为文件读写,打印,随机数,这些东西都不是纯的计算过程,而是涉及到外部世界的交互,依赖于机器,不在理论的范畴。你想写文件,那么是你的程序调用操作系统的 API,操作系统再执行 CPU 的指令,控制磁盘的芯片,磁头开始在盘面上写入原本位于内存或寄存器的信息。但是在图灵完备中,是不需要什么磁盘,磁头,内存的,这些都是理论之外的。也就是说一门图灵完备的语言,只要有无限的时间和空间,你绝对能用来解微分方程组,三体问题,甚至模拟整个宇宙,但这些语言可能不能在屏幕上打 Hello, world.
Chapter 1.1. The Elements of Programming
1.Why - 我为什么要创造Lisp?
因为,要构建,构建抽象。build abstraction。
因为,要模拟真实世界的运行。
因为,要构建抽象的语义,Lisp只是语法。
隐喻
使用咒语来召唤精灵,让精灵来帮忙做事。
定位:精灵就是process,咒语就是program。
Lisp只是一种咒语。不同的咒语,呈现不同的精灵形态。精灵还是同样精灵,但是行为模式根据咒语的范式而不同。
2.What - 抽象的实体是什么?答案是过程process
1.abstract beings are process
2.process manipulate other abstract tings called data
3.And data is process
What - process和program的关系是什么?
1.program is rules that direct processes.
2.program is rules that construct data.
What - process的隐喻是什么?
A computational process is indeed much like a sorcerer’s idea of a spirit.
Process是巫师的精灵的思想。(不是精灵,而是精灵的思想)
Why - 为什么不是精灵本身?而是思想?
因为,process是信息,精灵是物质,思想才是信息。
另外,精灵还是可以看得见摸得着的,思想是非物质的。
写在纸上的文字不是信息,运行于看不见的世界中的东西,才是思想。
What - program的隐喻是什么?
The programs we use to conjure processes are like a sorcerer’s spells.
Program是巫师的咒语
Why - 为什么?
因为,咒语可以操纵精灵
真正干事情的是精灵
process是精灵的灵魂
硬件是精灵的肉体
灵魂知道肉体的行为,导致物理世界的改变—0/1电压的改变
What - procedure和process的关系是什么?
The most significant of these features is the fact that Lisp descriptions of processes, called procedures, can themselves be represented and manipulated as Lisp data.
在Lisp中描述process的文字叫做procedure。
所以,咒语的载体就是procedure,咒语的具体内容就是具体的code。
一个procedure对应着一个未来跑在计算机里的process
另外,procedure有一个特性:Lisp的procedure自身也是Lisp data
即,代码即数据。
Why - 我为什么Lisp要将procedure设计成是Lisp Data?
The importance of this is that there are powerful program-design techniques that rely on the ability to blur the traditional distinction between “passive” data and “active” processes.
在Lisp中,procedures as data,这颠覆了这样一个传统—被动的data和主动的process。
这里要反问,为什么其他编程语言不将自身设计成data?
因为,其他语言妥协了,向自然语言妥协,想要讨好人类的语言习惯,所以放弃了形式的统一性。
而,Lisp没有妥协,保持了数学和自然语言的平衡,守住了底线,即语言即数据。语言即数学。咒语的内容可以是咒语本身,咒语可以谈论它自己。所以,Lisp天生就可以self-refer,就像人类可以天生自省。
Lisp可以思考他自己,就可以改变自己。这就是觉知力的本质。
也就是说,Lisp天生就具有觉知力。
如果将Lisp比喻成人类,那么其他编程语言就是动物。
3.Why I design the framework 我为什要设计出构建语言的框架思想?
— Chapter 1.1 The Elements of Programming
1.因为,这是构建这门手艺的核心原则。
2.因为,这是构建PL/Anything的材料。
3.因为,这是一种哲学思想。
What - 什么是语言的元素?
The Elements of Programming
1.Primitive expressions
- which represent the simplest entities the language is concerned with
- atom原子
- expressions表达式
- 数字也是一种expression
**Why - I need expressions? (— Chapter 1.1.1 Expressions) **
因为,如果要构建一门PL,第一步就是设计最小组成粒度的材料。
就像,乐高基础块之于乐高作品。
就像,转头之于盖房子。
2.Means of combination
- by which compound elements are built from simpler ones
- apply应用:表达式apply表达式
- 表达式的构建:表达式嵌套表达式
Why - I need combination ? (— Chapter 1.1.1 Expressions)
因为,有了基础块,就要考虑如何拼装基础块。
就像,乐高块的凸起按钮的设计。
3.Means of abstraction
- by which compound elements can be named and manipulated as units.
- define
- refer
Why - I need naming? (— Chapter 1.1.2 Naming and the Environment)
因为,可以更好的控制复杂度。
因为,可以枸建更复杂的工程。
为了,维持一个状态:name/Object pair 引出,Environment
Environment的本质:一块内存,存储key/value的store。
How - Environment的启发?
redis这样的key/value内存数据库。
可以构建分布式的interpreter。
网络版的lisp语言。工程化的网络化的编程语言。XLISP
How - 指导意义?
1.谁封装的更彻底/更友好,他就越容易赚到钱—找到商业模式。
一定要让人们,方便/愉悦的使用你提供的封装好的服务。
2.例如,apple的产品,将数据封装到罐子里,应用都不可以收工copy安装文件。反例windows的系统。
3.不让用户看到更多的选择,反而是一种用户友好行为。
Lambda calculate的3元素是什么?
1.primitive expressions
- 变量
- 抽象体
2.means of combination - 应用
3.means of abstraction - Lambda是匿名函数,所以,在Lamda函数中调用自身根本就是被禁止的。
How - the building framework of elements对我写recreating lisp这本书的指导意义是什么?
1.Primitive expressions:漩涡内环的一次旋转,即,Why&Infomation 原因/答案, 书里的信息多是答案what/how,我的工作就是填补why。
2.Means of combination:漩涡的连接。 (((why/Info)/Info`)/Info``)
3.Means of abstraction:漩涡的外环位移。Name of Chapter,章节的名字就是一种概念组合的抽象。所以,章节名字的定义原则是,要能表示这一章节的内容,而不是为了炫。
4.Why - I design Evaluating Combinations?我为什么要设计求值这个概念?
— Chapter 1.1.3 Evaluating Combinations
因为,要计算,要动起来,要运行。
因为,计算的本质就是组装,from lambda calculate
因为,组装的本质就是求值,from lisp
求值的主角是:combination
To evaluate a combination, do the following:
1.Evaluate the subexpressions of the combination.
2.Apply the procedure that is the value of the leftmost subexpression(the operator ) to the arguments that are the values of the other subexpressions(the operands)
Apply的本质是Bate规约,即,组装
Evaluate的本质是递归的调用Apply,直到遇到primitive element
所以,Apply和Evaluate形成了一个递归的内环。
Eval/Apply就是Lisp漩涡模型的核心内环。
Eval/Apply隐喻着宇宙的中心,旋转。
How - 对我的启发是什么?
找到我的漩涡的核心,每天要做的事就是重复的旋转它。
5.Why - I design Substitution Model? 我为什么设计Substitution Model?
** — Chapter 1.1.5 The Substitution Model for Procedure Application**
因为,求值的模式有两种。
第一种:Applicative order,即,先求值参数operands,再带入到operator
第二种:Normal order,即,后求值参数operands,什么时候要用到再求值
第二种像太极拳,先不发力,先蓄势,最后一下子再发力。这样可以省了很多过程中的发力,过程就是顺着走。但是难学难懂,很难工程化教学。(stream view)
第一种很像外家拳,按照招式走,一招是一招。(object view)
6.Why - I design Conditional Expressions and Predicates?我为什么设计了Conditional Expressions
** — Chapter 1.1.6 Conditional Expressions**
因为,如果没有过程中的test求值能力,构建的过程很受限制。
所以,为了让编程更有威力,即,更好的控制复杂度,所以,引入了condition Expressions.
本质,就是,过程中的特殊expression的求值。
所以,condition expression is special form procedure, not as the normal procedure.
所以,interpreter应该是对condition expression做了特殊处理,走了不同于procedure的逻辑分支。
例如:excercise 1.6
new-if与 if的不同之处,在于参数的求值。
=> (if #t (display “good”) (display “bad”)
good
=> (new-if #t (display “good”) (display “bad”)
badgood
7.Why - I import the example : Square Roots by Newton’s Method? 我为什么要引入牛顿解平方根的例子?
** — Chapter 1.1.7 Example:Square Roots by Newton’s Method**
因为,要定位,mathematical与procedure的不同,即,mathematical is what, procedure is how-to
因为,要定位,function与procedure的不同。function is belongs to mathematical.
因为,要展示,抽象的描述how-to
因为,要展示,递归。
(define (sqrt-iter guess x)
(if (good-enough? Guess x)
guess
(sqrt-iter (improve guess x)
x)))
哪怕只有一句逻辑,也可以封装成抽象abstraction,如下:
(define (improve guess x)
(average guess (/ x guess)))
(define (average x y)
(/ (+ x y) 2))
(define (good-enough? Guess x)
(< (abs (- (square guess) x)) 0.001))
(define (square x)
(* x x)
8.Why - I import the thought of Black-Box Abstraction?我为什么要提出黑盒抽象这个思想?
— Chapter 1.1.8 Procedures as Black-Box Abstractions
因为,要降低系统复杂性。
如果,所有子问题都开放,系统的关系链接就会过多,互相依赖过多,复杂性就暴涨了。
为了,让复杂性内聚,也就是让关系变少。所以才要构建Black-Box
具体如何让Lisp实现呢?
1.作用域的private
2.内部定义过程
3.block structure
What?本质上是什么?
本质上是procedures的组合。
之前有了expressions的组合。
在lisp中任意元素都可以自由组合。所以说,lisp像水。
后面会遇到Higher-Order Procedure, Closure Property都是基于Black-Box Abstraction这个思想。
How?启发?
1.在metanet上构建一个分布式PL。
2.将任何程序都看成一棵树
3.所以,要将程序写成形状均匀的树。大多数人的程序都不平衡。
Chapter 1.2 Procedures and Processes They Generate
1.Why I need to test the running of procedures and processes?
因为,我们需要练习“看见”代码。即,将代码解释成图像。
How to becoming an expert programmer?
The ability to visualize the consequences of the actions under consideration is crucial to becoming an expert programmer, just as it is in any synthetic, creative activity.
Why?
因为,这样会让我的神经元接近lisp的模式。将我的大脑驯化成Lisp Interpreter。
我的大脑,要成为这样一个Lisp Interpreter,就像黑客帝国一样,见到代码就能自动翻译成图像。
2.Why I talk about the two different Loop way: Linear Recursion and Iteration
— Chapter 1.2.1 Linear Recursion and Iteration
因为,我们要尝试不同的递归模式
在这两种循环中,我们可以尝到循环的本质。
状态的本质。
空间复杂度的区别。
What - 两者的本质不同是什么呢?
1.linear recursive : 面向过程(没有时间)(无状态)
2.linear iterative:面向对象(时间因素)(有状态)(将状态放在变量中,即,enviorment)
Chapter 1.3 Formulating Abstractions with Higher-Order Procedures
1.Why I design the concept Higher-Order Procedures?
This would place us at a serious disadvantage, forcing us to work always at the level of the particular operations that happen to be primitives in the language(multiplication, in this case) rather than in terms of higher-level operations.
Our programs would be able to compute cubes, but our language would lack the ability to express the concept of cubing.
Yet even in numerical processing we will be severely limited in our ability to create abstractions if we are restricted to procedures whose parameters must be numbers. Often the same programming pattern will be used with a number of different procedures.
To express such patterns as concepts, we will need to construct procedures as values.
Procedures that manipulate procedures are called higher-order procedures.
因为,这是尊从Black-Box Abstraction这个思想的演进。让Lisp更加自由,自由组后,Procedure可以作为First-class自由流转穿梭。
因为,要构建模型。即,某些算法部分不变,某些算法可变。不变的就是模型,将可变的部分开放出去,这是一种更高级的抽象。缠在一起的庖丁解牛级别的抽象。曲线的抽象。
启发,比特币的脚本,就是这种对外开放的procedures。潜入bitcoin内部的渠道。
抽象—链接—>抽象
这里的链接是内部深层的链接。
我之前的编程思维还只停留在refer这种简单链接维度。这就好比同样是解牛,我只能大块的砍,庖丁能深入的解牛。
What - 抽象视角是什么?
Procedure is Transaction
Transaction‘s input: Procedures as Arguments (Chapter 1.3.1)
Transaction’s output: Procedures as Returned Values (Chapter 1.3.4)
Procedure is the first-class element as Tx in bitcoin.
Procedures从此就像LEGO基础块有了上下链接的凸凹按钮。
Procedure/TX的链接
Procedure/TX的流通
2.Why I design Lambda?
— Chapter 1.3.2 Constructing Procedures Using Lambda
因为,能够直接定义Procedure
(Rather than define pi-next and pi-term, it would be more convenient to have a way to directly specify “the procedure that returns its input incremented by 4” and “the procedure that returns the reciprocal of its input times its input plus 2.” We can do this by introducing the special form lambda,)
Why?为什么需要直接定义Procedure?
因为,更加接近procedure的本体。匿名的procedure就是procedure本身。
因为,省去了define,就不需要refer,代码会更加紧凑,直白。可以在代码中,直接操作procedre,不用像以前那样,需要先定义,再引用,最终才能操作。
因为,define需要维护name-value对,在environment里。而lambda不需要,所以更简单,省事,按理说,效率也会提升。
Why I design let?
因为,let降低了思考的复杂度。所以,let是为人脑服务的,而不是为了程序服务。所以,let是糖,是给人脑吃的。程序根本没有感觉。
本质,let的本质就是一种语法糖,就是通过应用lambda的方式连接lambda的参数另一种表达形式。实质性的信息没有变化,只是表面功夫。
3.Why I design Higher-Order Procedures with these 2 ways: Procedures as Arguments & Procedures as Returned Values
1.Procedures as Arguments: Ability of LEGO Block Input
本质,Procedures就像LEGO块,有了链接上游的凸纽。
2.Procedures as Returned Values: Ability of LEGO Block output
本质,Procedures就像LEGO块,有了链接下游的凹钮
这样一来,Procedure就可以自由穿梭于任何其他的Procedure。无限自由的组合可能性开启。