[基础软件理论与实践] 第二节作业实现
作业信息
作业基本描述

任务一 实现一下 substitution
在实现 lambda 演算的 eval 函数中,需要 App 相当于函数的应用,其中就是将对应的绑定变量进行替换,这个替换过程就是 substitution
任务中要求实现 N[v/x] 就是把 N 里面的 x 用 v 来替换掉,N 是一个 lambda 表达式,因此可以针对表达式的分类情况进行枚举,逐一执行
// expr2[expr1/s] 在表达式2中 用 表达式1 把 s 给替换掉
// s, expr2 一般是一个 lambda fn
// 这个过程 感觉有点像 贝塔规约
let rec subst = (s, expr1, expr2) => {
// bind s and expr1
let substitution = subst(s, expr1)
// 根据 expr2 的情况枚举处理
switch expr2 {
| Var(ss) if ss === s => expr1 // 找到替换的目标 则 Var(ss) 换成 expor1
| Var(_) => expr2 // 没得换 直接返回 expr2
| Fn(ss, e) if ss !== s => Fn(ss, substitution(e)) // 如果 expr2 是个函数定义 并且参数没有和要替换的相同 替换 body 部份 并且保留参数 ss
| Fn(_, _) => expr2 // 要替换的参数 s 由于内部 expr2 遮蔽了,所以也换不得;同名但是不同含义
| App(a, b) => App(substitution(a), substitution(b)) // a,b 递归处理即可
}
}
完整代码: lec2 task1 Implement the substitution function
任务二 考虑 N[M/x] 中 M 有自由变量的情况
在 substitution 中,如果 M 有自由变量的话,就有一些麻烦的事情出现
比如 N = λx.xy, M = λa.ax, 如果要做的话N[M/y]按照 任务一 的实现,就会变成 λx.x(λa.ax),这个时候,加粗的 x 的含义就很尴尬,它其实不是原来的绑定变量,但是现在却被变成了和参数x绑定起来了.
所以在做这个处理的时候,我们要考虑 N 中是否有自由变量会和原本的参数名相同,如果相同的话,就会造成这种歧义.为了避免相同,我们可以根据 阿尔法 规则,先把 N 中的 x 换个不在 M 中自由变量里面的“名字”.
所以第一步,我们需要能够获得一个 lambda 表达式中,自由变量列表的途径: getFreeVar(e: lambda): list<string>,然后处理 Fn 的情况,进行分类讨论
// 从一个 expr 中得到自由变量列表
let getFreeVar = expr => {
let rec go = (ans, expr) => {
switch expr {
| Var(a) => list{a, ...ans}
| Fn(arg, body) => go(ans, body)->Belt.List.keep(it => it !== arg) // 排除掉绑定变量 arg
| App(fn, param) => go(go(ans, fn), param)
}
}
go(list{}, expr)
}
// 返回一个新的标识符
// getNewSymbol(i) !== i
let getNewSymbol = i => i ++ "_"
subst 的实现变化如下,主要是对 Fn 分支的变更
let rec subst = (s, expr1, expr2) => {
switch expr2 {
| Var(ss) if ss === s => expr1 // 找到替换的目标 则 Var(ss) 换成 expor1
| Var(_) => expr2 // 没得换 直接返回 expor2
- | Fn(ss, e) if ss !== s => Fn(ss, substitution(e)) // 如果 expr2 是个函数定义 并且参数没有和要替换的相同 替换 body 部份 并且保留参数 ss
- | Fn(_, _) => expr2 // 要替换的参数 s 由于内部 expr2 遮蔽了,所以也换不得;同名但是不同含义
+ | Fn(arg, body) =>
+ // Think about how substitution works on arbitrary terms, i.e. N[M/x] where M could contain free variables.
+ if arg === s {
+ // 否则如果绑定变量和当前替换的 s 相等的话,产生遮蔽的效果,替换不了了 原样返回
+ expr2
+ } else if getFreeVar(expr1)->Belt.List.has(arg, String.equal) {
+ // 如果 body 中绑定变量 arg 和 expr1 中有命名冲突需要进行处理
+ let symbol = getNewSymbol(arg)
+ let newBody = subst(arg, Var(symbol), body)
+ // 基于新的
+ Fn(symbol, substitution(newBody))
+ } else if arg !== s {
+ // 如果绑定变量和当前替换的 s 不相等的话,直接穿透 在 body 继续替换
+ Fn(arg, substitution(body))
+ } else {
+ assert false
+ }
| App(a, b) => App(substitution(a), substitution(b)) // a,b 递归处理即可
}
}
变更提交: lec2 task2 subst works on arbitrary terms
任务三 church number and arithmetic functions
补充实现 Nat.res
这里主要是补充 lec2-code 中的 src/Nat.res 中关于 Peano 和 Church 两种形式的 加法和乘法
// a + b
let rec peano_add = (n: nat, m: nat): nat => {
switch n {
| Z => m // 0 + k === k
| S(n') => peano_add(n', peano_succ(m)) // n + m <=> (n - 1) + (m + 1)
}
}
let rec peano_mul = (n: nat, m: nat): nat => {
switch n {
| Z => Z
| S(n') => // n * m <=> ((n - 1) + 1) * m <=> (n - 1) * m + m
peano_add(peano_mul(n', m), m)
}
}
// add = λnmfx.nf(mfx)
let church_add = (n: cnum<_>, m: cnum<_>): cnum<_> => (f, x) => n(f, m(f, x))
// let constTrue = (x, _) => x
// let constFase = (_, y) => y
// let ifThenElse = (x) => x
// isZero(church_zero) => T
// isZero(church_one) => F
// let isZero = (n: cnum<_>) => n((_) => constFase, constTrue)
// mul = λnmfx.f^(n*m)x mul m n => 对 f 迭代 n * m 次
let church_mul = (n: cnum<_>, m: cnum<_>): cnum<_> => {
// n * m
// <=> m + m + ... + m (一共 n 个 m 相加)
// <=> (...((m) + m) + m) + ...)
// <=> (...((0 + m) + m) + m) + ...)
// n => λnfx.f^(n)x 对 f 迭代 n 次
// m => λmfx.f^(m)x 对 f 迭代 m 次
// 记 F = λx.f^m 把 f apply 到 m 上 即 m(f)
// mulBody = F^(n)x 把 F appy 到 n 上,即 n(F)
f => n(/* F */ m(f))
}
疑问点记录: church_mul 本来是想通过课件中 lec2-part2.pdf#20 里面的乘法定义,用递归、 pred、isZero 组合来实现,但是失败了 pred(n) 是无法调用的,类型错误

使用 Lambda module 来模拟
这里是想用 任务一/二 中我们自己写的 lambda ast 来模拟 church number, 这个时候就可以用 课件 20 页的乘法定义来表达乘法了
open Lambda
// λfx.x
let church_zero = Fn("f", Fn("x", Var("x")))
// λfx.fx
let church_one = Fn("f", Fn("x", App(Var("f"), Var("x"))))
// λfx.f(fx)
let church_two = Fn("f", Fn("x", App(Var("f"), App(Var("f"), Var("x")))))
// λfx.f(f(fx))
let church_three = Fn("f", Fn("x", App(Var("f"), App(Var("f"), App(Var("f"), Var("x"))))))
let constTrue = Fn("x", Fn("y", Var("x")))
let constFalse = Fn("x", Fn("y", Var("y")))
let ifThenElse = Fn("x", Var("x"))
// λnfx.f(nfx)
// succ zero => (λnfx.f(nfx))(λfx.x) => λfx.f((λfx.x)fx) => λfx.fx => one
// succ one => (λnfx.f(nfx))(λfx.fx) => λfx.f((λfx.fx)fx) => λfx.f(fx)
let successor = Fn("n", Fn("f", Fn("x", App(Var("f"), App(App(Var("n"), Var("f")), Var("x"))))))
eval(App(successor, church_two))->show->Js.log2("succ church_two", _)
// λnmfx.nf(mfx)
// add one two => λfx.(one)f((two)fx) => λfx.(λx.fx)(f(fx)) => λfx.f(f(fx))
let add = Fn(
"n",
Fn("m", Fn("f", Fn("x", App(App(Var("n"), Var("f")), App(App(Var("m"), Var("f")), Var("x")))))),
)
eval(App(App(add, church_one), church_two))->show->Js.log2("add one two", _)
let iszero = Fn("n", App(App(Var("n"), Fn("z", constFalse)), constTrue))
eval(App(iszero, church_one))->show->Js.log2("is zero", _)
let pair = Fn("x", Fn("y", Fn("z", App(App(Var("z"), Var("x")), Var("y")))))
let fst = Fn("p", App(Var("p"), constTrue))
let snd = Fn("p", App(Var("p"), constFalse))
eval(App(fst, App(App(pair, church_one), church_two)))->show->Js.log2("fst pair one two", _)
let pred = {
let f = Fn(
"p",
{
let secondP = App(snd, Var("p"))
App(App(pair, secondP), App(successor, secondP))
},
)
let pc0 = App(App(pair, church_zero), church_zero)
Fn("n", App(fst, App(App(Var("n"), f), pc0)))
}
// λf.λx.f ((λf.λx.f ((λf.λx.x) f x)) f x)
// <=> λf.λx.f(fx)
// <=> church_two
eval(App(pred, church_three))->show->Js.log2("pred lambda", _)
let mul = {
let yCombinator = {
Fn(
"f",
App(
Fn("x", App(Var("f"), App(Var("x"), Var("x")))),
App(Var("f"), App(Var("x"), Var("x"))),
),
)
}
let f = {
Fn(
"f",
Fn(
"n",
Fn(
"m",
App(
// if(n=0) then 0
App(App(ifThenElse, App(iszero, Var("n"))), church_zero),
// else (m + f(n - 1)m)
App(
App(add, Var("m")),
{
let predN = App(pred, Var("n"))
App(App(Var("f"), predN), Var("m"))
},
),
),
),
),
)
}
App(yCombinator, f)
}
let churchNumberToInt = e => {
let rec go = (ans, exp) => {
switch exp {
| Fn(_, _) as n => if n == church_zero {
ans
} else {
go(ans + 1, eval(App(pred, n)))
}
| _ => assert false
}
}
go(0, eval(e))
}
churchNumberToInt(App(App(add, church_two), church_three))
->Js.Int.toString
->Js.log2("add 2 3 is", _)
churchNumberToInt(App(App(add, church_three), church_two))
->Js.Int.toString
->Js.log2("add 3 2 is", _)
churchNumberToInt(App(pred, church_three))->Js.Int.toString->Js.log2("pred 3 is", _)
补充记录
lambda 演算是颠覆我对函数式编程范式的认知的重大知识点.从前以为封装过程称为函数,抽得越细越多,就是在使用函数式编程.
但是如果不是有邱奇数这样的例子和万物都是表达式/函数,我是体会不到的它和过程式的代码也就是指令的不同之处的,最直接的一点就是赋值和let binding这样的区别.
之前曾在公司小组里面交流讨论过说,函数式中强调数据的不可变,倒不如说是因为所使用的编码语言(js / java / ts) 为了达到函数式中数学表达的“正确性”而所做的“约束”.个人理解,如果一个语言本身就是一个纯函数式语言,它是不用去强调不可变的数据操作的,其实都是一些“符号”,做的事情是在一些既定规则下,做符号的替换.
相关推荐
VDA 19.1_3rd edition_2026_English.rar
VDA 19.1_3rd edition_2026_English.rar
正交拉丁超立方(OLHC)分组与实验设计(DOE)评估的优化Python实现,移植自MATLA.rar
正交拉丁超立方(OLHC)分组与实验设计(DOE)评估的优化Python实现,移植自MATLA.rar
FCEMS燃料电池汽车能量管理控制、动力系统+驾驶员模型仿真、附说明文档及参考文献
内容概要:本文围绕燃料电池汽车能量管理控制系统(FCEMS)展开,系统阐述了其动力系统与驾驶员模型的仿真方法,并配套提供了详尽的说明文档及参考文献。研究深度融合线性车辆三自由度动力学模型与无迹卡尔曼滤波(UKF)技术,实现了对车辆横摆角速度、质心侧偏角及车速等关键状态的高精度估计,为优化能量管理策略奠定了坚实基础。整个仿真系统依托Carsim与Simulink联合仿真平台构建,有效保障了模型的真实性和工程实用性。此外,研究还涵盖了燃料电池供氢系统的精细化建模与先进控制策略,完整呈现了从核心部件建模到整车系统集成的全流程技术方案。; 适合人群:具备一定车辆工程、自动化或电力电子背景的研究生、科研人员及从事新能源汽车研发的工程师。; 使用场景及目标:①开展燃料电池汽车的能量管理控制策略研究与仿真验证;②进行车辆动力学状态估计与联合仿真平台搭建;③学习基于Simulink/Carsim的新能源汽车系统建模方法;④为硕士论文复现或科研项目提供全面的技术支持与参考。; 阅读建议:建议读者结合提供的仿真模型、PPT说明与参考文献,逐步复现仿真流程,重点关注UKF状态估计算法与能量管理逻辑的实现细节,同时利用所提供的网盘资源下载完整资料以便深入学习与扩展应用。
三相维也纳PFC整流器闭环仿真控制策略研究-中点电位平衡、无功补偿与双载波SVPWM(Simulink仿真实现)
内容概要:本文系统研究了三相维也纳PFC整流器的闭环仿真控制策略,聚焦于中点电位平衡、无功补偿以及双载波SVPWM调制三大关键技术问题。基于Simulink平台构建了30kW三相维也纳整流器的完整仿真模型,采用零序电流注入法实现直流侧中点电位的精确控制,有效抑制电压偏移,提升系统稳定性。结合无功电流补偿策略,显著改善输入功率因数,降低谐波畸变率。创新性地引入双载波SVPWM(马鞍波/羊角波)调制技术,优化开关调制过程,改善输出电压波形质量,减少开关损耗,提高整体转换效率。控制系统实现了对输入电流与输出电压的双闭环精确调控,仿真结果验证了该策略在动态响应速度、稳态控制精度及抗负载扰动能力方面的优越性能。; 适合人群:从事电力电子变换器设计、新能源发电系统、电动汽车充电技术及相关领域的高校研究生、科研人员以及具备电力电子与自动控制理论基础的工程技术人员。; 使用场景及目标:①为高功率密度、高效率PFC整流器的研发提供理论依据与仿真验证手段;②解决三电平整流器中常见的中点电位失衡难题;③探索先进SVPWM调制技术在实际系统中的应用潜力;④支撑新能源并网、工业电源、车载充电机等对电能质量要求严苛的应用场景下的核心电力电子装置开发。; 阅读建议:建议读者结合提供的Simulink仿真模型进行实践操作,深入理解控制器结构设计、零序注入实现方式、双载波生成逻辑及PI参数整定方法,并可通过修改负载条件或电网参数开展对比仿真,以全面掌握系统动态特性,后续可进一步推进至硬件原型实验验证。
汽车电动助力转向EPS系统建模仿真、二自由度+上下转向柱等、附参考文献
内容概要:本文围绕汽车电动助力转向(EPS)系统展开,重点介绍基于线性化模型的EPS系统建模仿真方法,涵盖二自由度车辆动力学模型与上下转向柱的动力学建模,深入探讨转向系统的控制策略设计与仿真验证。通过Matlab/Simulink工具实现系统建模与仿真分析,并结合Carsim与Matlab联合仿真手段,提升模型的真实性和工程参考价值。同时,研究中引入参考文献支撑,确保理论依据充分,内容兼具学术性与实用性。 适合人群:具备车辆工程或自动化相关背景,熟悉Matlab/Simulink仿真环境,从事汽车底盘控制、转向系统开发或智能驾驶研究的研发人员与研究生。 使用场景及目标:①开展电动助力转向系统(EPS)的建模与控制算法设计;②利用联合仿真平台(如Carsim与Matlab)进行车辆操纵稳定性分析;③研究转向柱动力学特性对转向手感的影响;④作为车辆动力学课程或科研项目的仿真案例参考。 阅读建议:建议读者结合文中提及的参考文献,系统学习EPS控制理论基础,并动手实践Matlab/Simulink仿真模型,通过调整控制参数与工况设置,深入理解系统响应特性,进一步拓展至多自由度整车模型与先进控制策略(如H∞、MPC)的应用研究。


