本篇前置:
- IncRe[1] CTM 0 Preface, Running the Example Programs
https://www.cnblogs.com/minor-second/p/15690392.html
基础信息
- 书名:Concepts, Techniques, and Models of Computer Programming
- ISBN:978-0-262-22069-9
- 1 Introduction to Programming Concepts
- Incentive:本学期课程有一门“约束式编程”,是一种非常有意思的paradigm. 想弄明白其在各种programming paradigm全景中的位置。
1 Introduction to Programming Concepts
1.1 - 1.3 A calculator, Variables, Functions
- 区分identifier和store variable这两个概念。当identifier的含义变化时,不“回溯既往”。既往的用的还是当时的取值(当时的store)
declare
V = 1
W = t(V 2*V)
declare
V = 2
X = t(V 2*V)
{Show [V W X]}
输出结果为
[2 t(1 2) t(2 4)]
注:如果不写第二个declare
,就会报错。因为这是尝试第二次赋值,而不是“重新declare”。这不允许!
- 函数:每次运行创建局部变量(请联系上一条的说法。不同次创建的局部变量之间identifier相同,store不同)
- 函数中递归调用自身:
Fact
(阶乘). 函数调用其它函数:Comb
(组合数调用阶乘)
1.4 - 1.5 Lists, Functions over lists
- 链表
- list是链表。空表是
nil
H|T
运算:5|[6 7 8]
得到[5 6 7 8]
[5 6 7 8]
只有.1
和.2
,即第一个元素和后面所有元素。
注:根本原因是用链表存储,而不是顺序存储
- list是链表。空表是
- Pattern Matching
case L of H|T then
,把L
试图匹配成H|T
形式,并赋值给H
,T
与declare
有类似性- 还可以加
else
表示不匹配的结果
- 还可以加
- 应用:把链表左侧加0 右侧加0 逐点相加
- Top-down方法:先知道求帕斯卡三角的行是由上一行左移+右移+相加,再单独写各个函数
1.6 - 1.7 Correctness, Complexity
- 程序语义semantics需要正确,实际执行的specification也要正确。我们相信Mozart developers, software companies, hardware manufacturers, and physicists都正确(否则检验就没法玩了)
the price of correctness is eternal vigilance
- 常见:数学归纳法(最简单)。可针对关于整数的程序
想把指数时间复杂度的变为了多项式时间复杂度:
else L in
L={FastPascal N-1}
{AddList {ShiftLeft L} {ShiftRight L}}
end
这里的L in
声明使得else
和end
可以使用它,加快运算,避免重复计算L
1.8 Lazy evaluation
Eager/lazy, data-driven/demand-driven
某种程度上相当于数学做题里的“设而不求”。比如
declare
fun lazy {Ints N}
N|{Ints N+1}
end
declare
L = {Ints 3}
{Show L}
{Show [L.1 L.2.1 L.2]}
输出
L<optimized>
[3 4 4|_<optimized>]
可以看到相当于先把无穷多个数设出(理论上都available,但是实际中不都取用。取到哪,算到哪)
如果不加lazy
输出
*** Warning: Mozart: virtual memory exhausted.
Process Oz Emulator exited abnormally with code 1
lazy还有一个特性
The lazy version avoids redoing all this work. It is always ready to continue where it left of
换句话说,占用空间多。计算过的都记录下来
1.9 Higher-order programming
- 把前面加法换成异或(用来算奇偶),需要重新写函数吗?
- 函数作为参数,就是“高阶”
例如fun {GenericPascal Op N}
,Op
是函数。
Op
在{OpList Op {ShiftLeft L} {ShiftRight L}}
这种地方被调用,而
OpList
中,Op
又在{Op H1 H2}
被调用。
可以看到{Op H1 H2}
中Op
处于大括号中首位,说明它是一个函数。 - 于是可以写出定义
fun {Add X Y} X+Y end
fun {Xor X Y} if X==Y then 0 else 1 end end
fun {FastPascal N} {GenericPascal Add N} end
(注:fun
然后连写两个{...}
,把前面的定义为后面的)
...
注:也可以调用现有模块Number
中的加法(写成前缀表达式)
1.10 - 1.11 Concurrency, Dataflow
各部分独立(除非指定了要通信)。比如想计算a*b+c*d
,就有两个独立部分
还比如说
thread P in
P = ...
{Show P}
end
{Show 99*99}
两部分互相独立,上面thread
里可能需要计算很久,这样下面的先输出
What happens if an operation tries to use a variable that is not yet bound? From
a purely aesthetic point of view, it would be nice if the operation would simply
wait. Perhaps some other thread will bind the variable, and then the operation can
continue. This civilized behavior is known as dataflow.
既然有了thread(时间有快有慢),就有可能等别的thread
declare X in
thread {Delay 1000} X=99 end
{Show start} {Show X*X}
马上出start
,然后等1000ms,才出现9801
核心:互相独立,互不影响,不改变结果
总结和问答练习
- Q: 一般状态下下面程序输出顺序是什么?
declare X in
thread {Delay 1200} X=99 end
{Show X*X}
declare X in
thread {Delay 1100} X=98 end
{Show start} {Show X*X}
A: start
,9604
,9801
. 因为注意每个{Show X*X}
中的X
只和其前面线程运行结果有关,两者相互独立。特别地,程序中,源码写在前面的线程不一定实际出结果在前面。两者在程序中书写的顺序不本质。