SICP 课程总结 & 复习

SICP 课程总结 & 复习

小作文

有赖于那个终极的、伟大的、命定的教务系统,我选上了这门课:SICP,Structure and Interpret of Computer Programs,计算机程序的构造与解释。

 

作为一门编程课,SICP颇有一种包罗万象的气质:讲授三种编程语言;涉及一众编程范式;更由于两位授课老师都在程序设计语言(Programming Languages)界颇有建树,这门课同样包含了许多考试范围外的 PL 概念。如果你常常在课程群里提问,甚至还能了解到如何证明TSP可以在多项式时间内验证之类的问题。总之是洋洋大观。

由于上课的时候说到许多概念都是英文,所以具体内容我也用英文写一下吧(

The curriculum SICP concentrates on the idea of abstraction, using Python, Scheme and SQL three languages to reveal various programming paradigm including imperative, declarative, functional, object-oriented and generic programming, and to cover numerous advanced concepts like higher-order function, closure, macro, polymorphism, metaclass and so on.

 

一般的评论都认为南大的SICP是一门实验性、革新性的课程。

先说实验性:

首先,今年也就刚刚是SICP第二年开课。按冯新宇老师的比喻,我们也就算是黄埔二期生,很有实验性。

其次,计算机系的后续课程都是基于C/C++的,而SICP很有实验性色彩地,全然不涉及C/C++内容。因此,虽然SICP和程序设计基础(讲授C/C++的传统课程)是二选一的平行课,基本没有学生会单选SICP,应该也只有我这样的转专业抽签倒霉蛋例外了。

如果还要再说一点的话,SICP破天荒地用了五位助教。五位助教老师一方面高山仰止、尽职尽责,一方面和大家打成一片,骚话连篇。几位助教在群里耐心解答各类问题,还会额外地讲一些类型系统、复杂度分析的知识,进一步扩充了SICP的课程内容,总之就是非常地好。

然后是革新性:

革新性其一就在之前说到的课程内容。据说SICP涉及的一些概念,比如闭包(closure),在南大甚至国内高校的绝大部分课程中都不会涉及。而且就编程教育而言,面向无基础初学者的SICP,竟然涉及了如此多难懂甚至晦涩的概念,纵然是蜻蜓点水、浅尝辄止式地涉及,对初学者来说无疑是一种挑战。

革新性其二,在于我们这门课的两位老师:冯新宇、李樾。据说国内计算机系有 PL 方向的高校都是凤毛麟角,何况有这两位优秀的老师。让他俩来讲课也或许有PL方向的意思(?)

 

最后做一点批评?

上课中途,某认识的大佬听说 SICP 非常硬核,于是来旁听。待了几节课,苦于题目描述缺乏条件限制一类的科学性、严谨性,又考虑到 SICP 虽然涉及颇多概念,但是本质上仍然是一门编程课:收获不会太大,于是跑路了。这几点我也算感同身受。

SICP 本质上是编程课,但用了一种特别的方式把内容串联在了一起。对于初学者来说难度实在颇大;对于已经具有一定开发经验的同学来说,大部分内容又显简单。定位实际上有些奇怪。

 

Lecture-1: Course Introduction

  • 诶,我们这个课可是很难的哦。退课得抓住机会哦。

此外稍微提了一下 Python 的 function call ,也就是分三步走的方法。

也就是分成

  1. Evaluate the operator subexpression
  2. Evaluate each operand subexpression
  3. Apply the value of the operator subexpression to the values of the operand subexpression

这里给出例子:

image-20210209150907568

 

Lecture-2: Names & Functions

Names

Name 和 Value 之间存在着一个绑定( Binding )关系。这里的 Value 可以是 Interger, Floats, Strings, Booleans, 甚至可以是 Function。

这种 Binding 关系是由赋值语句( Assignment )实现的。

x = 1
y = 2
x = y * 2

执行赋值语句时,我们首先 evaluate 等号右侧的值,然后将这个值与等号左侧的 Name 绑定。

Functions

声明函数的语法是:

def <name>(<parameters>):
	<body>
	return <return expression>

课程使用 python tutor 进行可视化展示。

 

Lecture-3: Control

不写 return 的函数默认会返回 None

print() 是一个典型,这个函数返回的是 None ,副作用( Side Effect ) 是输出。要注意区别 print 的输出和交互模式的回显。

Condition

if 语句的语法表现为:

if <conditional expression>:
    <suite of statements>
elif <conditional expression>:
    <suite of statements>
else:
    <suite of statements>

当然地,这里的 elifelse 都是可以省略的。

Iteration

 

Lecture-15: Python Inheritance

Attributes: Look up & Assignment

简单说来,Attribute 有两个类型:class attribute & instance attribute。

做 Attribute Look up 时,会优先找 instance attribute,找不到时再找 class attribute,如果还是没有找到,就继续上诉到父类,直到找到为止。如果最终还是找不到,就返回一个 attribute error 。

做 Attribute Assignment 时,考虑 dot expression 的两个部分:

<expression>.<name>

如果 <expression> 是一个 instance,那么就创建或修改对应的 instance attribute

如果 <expression> 是一个 class,那么就创建或修改对应的 class attribute

 

当然,class attribute 和 instance attribute 是有可能重名的,于是情况就会变得稍迷惑一些。

考点都在这个例子里,大概:

class Account:
    interest = 0.02
    def __init__(self, name_str):
        self.name = name_str

jim_account = Account('Jim')
tom_account = Account('Tom')

###################################

>>> tom_account.interest
0.02
>>> jim_account.interest
0.02
>>> Account.interest = 0.04
>>> tom_account.interest
0.04
>>> jim_account.interest
0.04
>>> jim_account.interest = 0.08
>>> tom_account.interest
0.04
>>> jim_account.interest
0.08
>>> Account.interest = 0.05
>>> tom_account.interest
0.05
>>> jim_account.interest
0.08

 

Inheritance, Multiple Inheritance

Inheritance 写法上很简单:

class <Name>(<Base Class>):
    <suite>

子类会顾名思义地继承父类的 class attributes,包括所有的 methods。

可以在 <suite> 部分任意的覆写父类的内容,也当然可以加入新的。没有覆写的attributes一律默认使用父类的。

 

Multiple Inheritance 同样很容易写:

class <Name>(<Base1>, <Base2>, ...):
    <suite>

当然,这里不可避免地会涉及 Diamond Problem。Python的处理办法是Method Resolution Order,说实话我觉得相当迷惑。还是 C++ 写起来安心(

樾哥提到 Python 并不适合大规模的开发使用,写一点精短的小程序的话,这样的设计应该也还受用吧。

这个应该不是重点。

 

Quiz

难倒是不难,但是这是人写的代码吗……

class A:
    z = -1
    def f(self, x):
        return B(x - 1)
    
class B(A):
    n = 4
    def __init__(self, y):
        if y:
            self.z = self.f(y)
        else:
            self.z = C(y + 1)
            
class C(B):
    def f(self, x):
        return x

Quesion:

  1.  >>> C(2).n
     ???
    
  2.  >>> a.z == C.z
     ???
    
  3.  >>> a.z == b.z
     ???
    
  4. Which evaluates to an integer?

    b.z
    b.z.z
    b.z.z.z
    b.z.z.z.z
    

 

答案是 4, True, False, b.z.z.z

 

Extensions:

  1. The definition of expressions

    https://docs.python.org/3/reference/expressions.html

  2. Metaclass, duck type and other things about type system

    Python中的 type 就是一个 metaclass ,也就是可以创建其它 class 的,一种更加形而上的 class。

    >>> type(jim_account)
    <class 'Account'>
    >>> type(Account)
    <class 'type'>
    

    而如果你试图用 Python 研究类型系统,得到

    >>> type(type)
    <class 'type'>
    

    也不必惊讶,这是 duck type 的缘故,参见

    https://en.wikipedia.org/wiki/Duck_typing

  3. Inheritance, composition and mixin

    Inheritance 是个很恐怖的东西。课件里写到,Inheritance helps code reuse but NOT for code reuse。继承的有两个主要的坏处,一是破坏封装,你不得不上溯父类;二是可能会继承冗余的信息。我印象中网上关于继承和耦合性还有很多有意思的钓鱼文。

    实际上在做和 class 有关 code reuse 的时候,inheritance, composition 和 mixin 都是可能的选项。

    可以参见

    https://naildrivin5.com/blog/2012/12/19/re-use-in-oo-inheritance.html

    说实话mixin的理解还是有点难度的。

  4. Diamond Problem

    http://www.srikanthtechnologies.com/blog/python/mro.aspx

 

 

Lecture-16: Python Special Methods

Special Methods

简单地说就是两个函数 str()repr() ,传进某个 instance 就会给出一个显式的字符串表达。

其中 str() 的返回结果更加 human readable,而 repr() 很大程度上是给解释器看的。

譬如说

>>> from fractions import Fraction
>>> half = Fraction(1, 2)
>>> repr(half)
'Fraction(1, 2)'
>>> str(half)
'1/2'

特别地,如果没有实现 str() 的话调用会默认返回 repr() 的结果。

这两个函数就有多态( Polymorphism )的性质。

 

实现上很简单:

class Ratio:
    def __init__(self, numerator, denominator):
    	self.n = numerator
        self.d = denominator
    def __repr__(self):
        return "Ratio({0}, {1})".format(self.n, self.d)
    def __str__(self):
        return "{0}/{1}".format(self.n, self.d)

 

实现重载操作符也是类似可行的:

class Ratio:
    def __init__(self, numer, denom):
        from math import gcd
        self.n, self.d = numer // gcd(numer, denom), 
                         denom // gcd(numer, denom)
    def __repr__(self):
        return "Ratio({0}, {1})".format(self.n, self.d)
    def __str__(self):
        return "{0}/{1}".format(self.n, self.d)   
    def __add__(self, other):
        self = Ratio(self.n * other.d + self.d * other.n,
                     self.d * other.d                    )

 

重载操作符的时候常常会遇到 <class A> + <class B> 能用,<class B> + <class A> 就不行了的情况。这时候只需要添加一行 __radd__ = __add__ 就可以了。

 

Extensions

  1. Polymorphism

    分成三类 Ad Hoc Polymorphism, Parametric Polymorphism & Inclusion Polymorphism。课件上破天荒给了C++代码示例:

    // Ad Hoc Polymorphism
    foo(int) {}
    foo(string) {}
    
    // Parametic Polymorphism
    Template<typename T>
    T foo(T x, T y) {
        return (x > y)? x: y;
    }
    foo<int>(3, 7)
    foo<char>('h', 'k')
        
    // Inclusion Polymorphism
    T v;	// T has many types
    v.foo();
    

    在这恐怖的课堂看见C++,就像回家了一样(

    参见

    https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

 

Lecture-17: Python Linked Lists & Trees

就是实现两个类:链表和树

感觉没啥可写的,这节课说实话没啥信息量,放在这里应该是做一个 class 和 OOP 的总结,然后引入链表给 Scheme 开个头吧。

 

Lecture-18: Scheme

尽管 Scheme 的代码未必好看,但是它的设计是很优美的。

Scheme 只有 expressions,expressions 也只分两种:

  1. Atomic expressions 最基本的单元,例如

    3, 5.5, -10, #t, #f
    +, modulo, list, x, foo
    
  2. Combinations,也就是把 Atomic expressions 组合起来,大概形如

    (<operator><operand1><operand2>...)
    

Combination 的执行和 Python 中的函数调用类似,分三步走

  1. Evaluate the operator
  2. Evaluate the operands
  3. Apply

 

然后讲到一些 Special Form Expressions

首先是 define ,看代码就行了,没啥可说的

scm> (define x (+ 3 4))
x
scm> x
7
scm> (define x (+ x 5))
x
scm> x
12

第三行代码体现了刚刚三步走的特质。 

此外,define 还可以定义函数,有语法:

(define (<func-name> <param1> <param2>)<body>)

顾名思义即可,给一个例子:

scm> (define (square x) (* x x))
square
scm> square
(lambda (x) (* x x))
scm> (square 4)
16
scm> (square -10)
100

看见第四行的那个 lambda 了没有?想必你也猜中了,可以直接使用 lambda 描述函数,而 define 的这种写法更像是 name 和 lambda function 绑定时的一种语法糖。

scm> ((lambda (x) (* x x)) 5)
25
scm> (define square (lambda (x) (* x x)))
square

 

然后是 if,有语法:

(if <predicate> <if-true> <if-false>)

顾名思义。

其中 <if-false> 可以省略。if 支持短路。特别地,Scheme 中唯一的 false 值就只有 #f ,比那些 Python 啊 Javascript 啥的不知道强到哪里去了。

scm> (if #t 3 5)
3
scm> (if 0 (+ 1 0) (/ 1 0))
1
scm> (if (> 10 1) (* 5 6))
30
scm> (if (not 4) 1 (if #f 5 6))
6

 

学到这里已经可以写出很多代码了,比如实现一个阶乘:

(define (fact n)
  (if (<= n 1) 1
      (* n (fact (- n 1)))))

 

Lecture-19: Scheme more

Pairs an Lists

Scheme 提供了很方便的构造链表的方法,可以玩出很多花子。

提供了四个主要的关键字:

  1. nil 表示空,作为链表尾部的标识。和 NULL 一样。
  2. cons 构造链表
  3. car 返回链表的首个元素
  4. cdr 返回链表的剩余部分

举例而言,是

scm> (define lst (cons 1 (cons 2 (cons 3 nil))))
x
scm> x
(1 3)
scm> (car x)
1
scm> (cdr x)
(3)

这里回显的机制稍显微妙,再看几个例子:

scm> (define x (cons 1 (cons 2 (cons 3 (cons 4 nil)))))
x
scm> x
(1 2 3 4)
scm> (car x)
1
scm> (cdr x)
(2 3 4)

此外,群里还提到过 carcdr 的复合语法糖……

image-20210109144341475

 

最后是另一种数据构造的语法糖,

比如 (define x (cons 1 (cons 2 (cons 3 (cons 4 nil))))) 就可以直接写成

(define x (list 1 2 3 4)) 或者 (define x '(1 2 3 4))

而且这里的 ' 还不止是构造 list 而已,实际上这被称为 quote ,任何跟在 quote 后面的东西都会原样显示。譬如:

scm> (define a 1)
a
scm> (define b 2)
b
scm> (list a b)
(1 2)
scm> (list 'a 'b)
(a b)
scm> (list 'a b)
(a 2)

这使得 quote 在许多情况都会有相当大的差别。

 

Tail Recursion

尾递归优化可以解决非常头疼的递归超限爆栈问题,当然只是其中一类。

当递归调用发生在 tail context 中时,就可以称作 tail call,这种情况就可以应用尾递归优化。这时,当前层的栈帧不再存储有效信息,因而没有留存的必要,也就不会出现爆栈的情况了。

特别地,我们可以看两个例子:

(define (fact n)
  (if (= n 0) 1
      (* n (fact (- n 1)))))

此处 fact 调用并不是 tail context ,最后一步执行的实质上是乘法,这导致当前层栈帧仍然需要存储变量 n 的值。我们稍换一个写法就可以得到可优化的程序:

(define (fact n)
  (define (helper n res)
    (if (<= n 1) res
        (helper (- n 1) (* n res))))
  (helper n 1))

 

最后给出一些 tail context 的例子

(and expr1 expr2 expr3*)
(+ expr1 expr2)
(if expr1 expr1* expr2*)
((lambda (expr1) expr2*) expr3)

代码中带 * 的都属于 tail context 的范畴。

 

Lecture-20: Scheme Interpreters

SICP 的 Project4 要求用 Python 写一个 Scheme 的解释器

这节课算是为 Project4 做的某种铺垫吧,大概就是讲讲 Interpreter 是怎么工作的,让大家写 Project 的时候能舒心点。

Scheme 解释器的核心是 REPL (Read-Eval-Print Loop),顾名思义,就是等待输入,等到了做 Evaluate 把结果输出的循环过程。

Read

首先考虑 Read 。读入用户指令后要经由 Lexer 和 Parser ,也就是 Lexical Analysis 和 Syntactic Analysis ,词法分析器与语法分析器。

Lexer 将用户输入转化成一系列 tokens ,也就是 literals, names, keywords, delimiters 等一系列东西。

之后,Parser 会接手这些 tokens ,构造成有意义的形式。在我们写的这个 Scheme 解释器中就会按照括号构造成链表。一般的 Parser 还会做语法检查,我们写的就省略了这个功能。

 

具体是构造成什么样呢?考虑 Scheme 设计中最巧妙的一个特性是,expression 实质上都是 lists

比如说 expression (+ 1 (* 2 3)) 实质上和 list (+ 1 (* 2 3)) 是没有差别的,而 (eval '(+ 1 (* 2 3))) 就完全等同于表达式求值。在这个意义上,我们只需要把 Scheme 语句翻译成对应的结构即可,具体地说是下面这种结构:

class nil:
    def __repr__(self):
    	return 'nil'
    nil = nil()

class Pair:
    def __init__(self, first, second):
        self.first = first
        self.second = second
	def __repr__(self):
		return 'Pair({0}, {1})'.format(self.first, self.second)

给的例子是:

> (if (< x 0) 1 (+ x 1))
Pair('if', Pair(Pair('<', Pair('x', Pair(0, nil))), 
		   Pair(1, Pair(Pair('+', Pair('x', Pair(1, nil))), nil))))
> 'hello
Pair('quote', Pair('hello', nil))

 

Evaluate

实现 evaluate 时只需要依照之前的三步走原则即可,直接看两个例子:

(+ 2 (* 4 1) 5)
eval(Pair('+', Pair(2, Pair(Pair('*' , Pair(4, Pair(1, nil))), Pair(5, nil)))))
	eval('+')
    eval(2)
    eval(Pair('*', Pair(4, Pair(1, nil))))
    	eval('*')
        eval(4)
        eval(1)
        apply(BuiltinProc(scheme_mul), [4, 1])
    eval(5)
    apply(BuiltinProc(scheme_add), [2, 4, 5])
(define (f x) (+ x 1))
(* (f 3) 2)
eval(Pair('*', Pair(Pair('f', Pair(3, nil)), Pair(2, nil))))
	eval('*')
    eval(Pair('f', Pair(3, nil)))
    	eval('f')
    	eval(3)
        apply(λ, 3)
        	eval(Pair('+', Pair('x', Pair(1, nil))))
            	eval('+')
            	eval('x')
                eval(1)
                apply(BuiltinProc(scheme_add), [3, 1])
	eval(2)
    apply(BuiltinProc(scheme_mul), [4, 2])

 

Lecture-21: Scheme Macros

基于上一讲提到的 Scheme 中 expression 等同于 lists 的特性,我们可以用 List 构造 Scheme 语句,从而写出很有技巧性的代码。一个简单的例子是:

scm> (define x 1)
x
scm> (define y (list '* 'x 'x))
y
scm> (eval y)
1
scm> (define x 2)
x
scm> (eval y)
4

考虑这种写法的本质,我们构造了某种函数,不 evaluate 参数的值就直接传入,最后传出的结果也不进行 evaluate 操作。也就是某种 lazy-evaluation function 。

Scheme 也为这种函数提供了语法糖: define-macro 。我们可以用 define-macro 在这个纯纯的函数式编程语言里,叛逆地实现 for 循环:

(define-macro (for var in lst do expr)
	(list 'map (list 'lambda (list var) expr) lst))

如果嫌弃 list 和 quote 写得太多,还有 Quasi-Quotation 语法糖。Quasi-Quotation 同样可以构造 List ,不过与 quote 恰好相反,在 Quasi-Quotation 中的内容全部会 lazy-evaluate ,只有前面加了逗号的不会。

说起来总归没有代码容易:

(define-macro (for var in lst do expr)
	`(map (lambda (,var) ,expr) ,lst))

 

Lecture-22: Scheme Stream

delayforce 是更直接的 lazy evaluation 方法。delay 的语句就处于 lazy-evaluation 的状态,当且仅当被 force 作用了才会被 evaluated。

而 Stream 本质上是 Lazy-evaluation List,和普通 List 差别不大。

只需要把 cons 换成 cons-stream ,把 cdr 换成 cdr-stream 即可。

(这也是语法糖,delay consforce cdr 嘛)

具体写法上有例子:

(define ones (cons-stream 1 ones))
(define (nats start)
  (cons-stream start (nats (+ start 1))))

(define (add-stream s1 s2)
    (cons-stream (+ (car s1) (car s2))
    	(add-stream (cdr-stream s1)
    		(cdr-stream s2))))

(define ints (cons-stream 1 (add-stream ones ints))

 

Lecture-23: SQL I

这 Declarative programmming language 真的也算编程语言,吗

 

直接看例子得了(

CREATE TABLE parents AS
    SELECT "delano" AS parent,	"herbert" AS child	UNION
    SELECT "abraham",			"barack"			UNION
    SELECT "abraham",			"clinton"			UNION
    SELECT "fillmore",			"abraham"			UNION
    SELECT "fillmore",			"delano"			UNION
    SELECT "fillmore",			"grover"			UNION
    SELECT "eisenhower",		"fillmore";

这样会生成一个叫做 parent 的表,内容是:

parent child
delano herbert
abraham barack
abraham clinton
fillmore abraham
fillmore delano
fillmore grover
eisenhower fillmore

 

SELECT 还可以顾名思义地从表格中选出一些东西,语法是:

SELECT [columns] FROM [table] WHERE [condition] ORDER BY [order] [ASC/DESC] LIMIT [number];

比如有

sqlite> SELECT * FROM parents ORDER BY parents DESC;
sqlite> SELECT child FROM parents WHERE parent = "abraham";
sqlite> SELECT parent FROM parents WHERE parent > child;

这个星号还挺离奇的,搞得我还觉得能支持正则表达式。实际上不能,星号就是代表整张表格。

 

此外我们还可以把两个表格合并,不过这合并实在是简单粗暴……

比如,有两个表格 table1table2

col1 col2
a b
c d
col3 col4
e f
g h
i j

SELECT * FROM table1, table2 就会得到

col1 col2 col3 col4
a b e f
a b g h
a b i j
c d e f
c d g h
c d i j

显然,这样产生的表格会有大量冗余信息,我们还需要配合 WHERE 关键字进行进一步筛取。

这里两个表格列的名称都是互异的,但相同时就会导致问题。为了区分,我们可以使用 AS 关键字,甚至可以把一个表格和自己合并起来。

SELECT * FROM parents AS a, parents as B WHERE a.parent == b.parent

 

还要补充一点,是使用 || 可以合并字符串,例如

sqlite> SELECT "hello," || " world";
hello, world
sqlite> SELECT name || " dog" FROM dogs;
abraham dog
barack dog
clinton dog
delano dog
eisenhower dog
fillmore dog
grover dog
herbert dog

 

Lecture-24: SQL II

Aggregation

SQL 还提供了一些 Aggregation ,和 excel 那些内置函数差不多。说真的,这玩意不就是个命令行 excel 吗(

课间上提到的有 MAX, MIN, AVG, COUNT, SUM 嘛,接着顾名思义咯。

SELECT COUNT(*) AS count FROM dogs;
SELECT SUM(age) AS sum_age FROM dogs;

大概就写出这样的东西。

 

Group

就是按照某个表达式对表格各行分组,如

SELECT fur, AVG(age) AS avg_age FROM dogs GROUP BY fur;

这里表达式只是单纯的 fur 的值,实际上可以写成各种形式。

如果稍微试一试就能发现 WHERE 的位置没法填 Aggregation,我们又想做这种筛选,就可以使用 HAVING 关键字。一个例子是:

SELECT fur, AVG(age) AS avg_age
    FROM dogs
    GROUP BY fur
    HAVING COUNT(*) > 1;

总之,完整的、复杂的 SELECT 语法是:

SELECT [columns] FROM [table]
	WHERE [condition]
	GROUP BY [expression]
	ORDER BY [order] [ASC/DESC]
	LIMIT [number]
	HAVING [expression];

 

Mutating Tables

首先是 CREATE ,和第一讲区别不大。

CREATE TABLE [name]([columns]);
CREATE TABLE parents(parent, child);
CREATE TABLE dogs(name, fur, phrase DEFAULT 'woof');

DEFAULT 关键字作用是,添加 row 的时候如果缺省值就会自动填入。

CREATE 完了就可以向里面添加值了,语法形如

INSERT INTO [table]([columns]) VALUES([values]), ([values]);

具体地可以写出这些形式:

INSERT INTO dogs(name, fur) VALUES('fillmore', 'curly');
INSERT INTO dogs VALUES('delano', 'long', 'hi!');
INSERT INTO dogs(fur, phrase) VALUES('curly', 'bark');

当然还可以更新和删除:

UPDATE [table] SET [column] = [expression] WHERE [condition];
DELETE FROM [table] WHERE [condition];

UPDATE dogs SET phrase = 'WOOF' WHERE fur = 'curly';
DELETE FROM dogs WHERE fur = 'curly' and phrase = 'WOOF';
UPDATE dogs SET fur = 'short';

最后是我最喜欢的

DROP TABLE [IF EXISTS] [name];

DROP TABLE dogs;
DROP TABLE IF EXISTS parents;

搁这写文档呢(

总之 SQL 是很简单的。樾哥提到下一代编程语言很可能就是以 declarative PL 为核心进行拓展的,原因大概也就在这里。简单而贴近自然语言,不需要在乎设计细节与效率。

当然,我作为肚子里货不太多的,冥顽不灵的 C/C++ 狂信者,觉得这样的设想还是相当遥远的。

原文地址:https://www.cnblogs.com/Shimarin/p/14253291.html