FPDIS 4.Functions and State
这周的课程没有作业,全是视频
Functions and State
首先我们需要回忆函数求值的过程,举一个例子
1 | def iterate(n: Int, f: Int => Int, x: Int) = |
在这个定义下,求iterate(1, square, 3)
1 | iterate(1, square, 3) |
在这个变换过程中,可以随时选择任意一个路径化简,而不影响结果
对于if (1 == 0) x else iterate(1-1, square, square(3))
iterate(1-1, square, square(3))
if (1 == 0) x else iterate(1-1, square, 3 * 3)
以上两者是等价的。
因为最后的结果总是会交汇的。
这个规律有个难记的名字叫 Church-Rosser Theorem
上面的情况适用于没有状态转换的情况,如果我们引入的状态/副作用呢?
以存款取款为例
1 | class BankAccount { |
在REPL里测试下
1 | scala> val acc = new BankAccount |
对于有状态类,我们对它进行同样的操作,结果会不同
Identity and Change
如果用E
来代替抽象的表达式,如果
val x = E; val y = E
并且x
等于y
那么我们可以把上述表达式改写成val x = E; val y = x
这样的性质叫做引用透明(referential transparency)
这有什么好处呢?
- 可以帮助证明程序的正确性(参照之前的数学归纳法证明)
- 可以便于重构和修改代码(由于不会修改到可变的状态)
- 可以通过记忆化,公共子表达式消除、惰性求值、或并行化来优化代码
但是现实生活中很多时候不满足这个性质
以之前的账户来举例
1 | val 你 = new BankAccount |
你和我都开了一个账户,我们的账户显然不一样。
为什么不一样呢?因为BankAccount
中存放了可变的值余额
Loops
首先看一个计算乘方的例子
1 | def power(x: Double, exp: Int): Double = { |
如果是习惯了命令式写法的选手,会觉得这个代码真是莫名其妙
为什么我不能直接对传入的参数修改,还得多此一举地声明两个新的变量r
和i
还记得上一个小节提到地引用透明
吗?其实这里的while
循环不是必要的,我们可以用函数来消除其中不必要的状态量r
和i
1 | def WHILE(condition: => Boolean)(command: => Unit): Unit = { |
啊这,为什么写成这个鬼样子,原版的while
不是简单多了?改成递归不怕爆栈吗?说好的参数是不能修改的,不就成死循环了?
不要急,下面会逐一解释。
我们可以看到,这个函数是柯里化的,第一个参数和第二个参数都是call by name
之所以设计成call by name
,是为了能在每次调用时,都能感知到状态的变化,而不是传入时立即计算出结果,
之后就不变了,所以不会死循环。
另一方面,函数的返回值和自身的签名相同,或者是一个基本类型(对,Unit也是基本类型)
而这恰好满足了尾递归优化的条件,编译器会帮助我们将其展开成迭代的方式,这样只会用到常量大小的栈空间,从而避免的爆栈(StackOverflow)
1 | scala> def WHILE(condition: => Boolean)(command: => Unit): Unit = { |
Extend Example: Discrete Event Simlation
本周课程的剩余时间,主要讲解如何实现一个模拟电路
由于个人的兴趣(模电数电的阴影),下面的内容就跳过了(可能之后会补上)
有兴趣的可以去了解下,如何用chisel3实现5级指令流水线
版权声明:
除另有声明外,本博客文章均采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。
分享