[基础软件理论与实践] 第二节作业实现

Thankssunhine 2022-11-26 11:04:19

作业信息

作业基本描述

img

任务一 实现一下 substitution

在实现 lambda 演算的 eval 函数中,需要 App 相当于函数的应用,其中就是将对应的绑定变量进行替换,这个替换过程就是 substitution
任务中要求实现 N[v/x] 就是把 N 里面的 xv 来替换掉,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 里面的乘法定义,用递归、 predisZero 组合来实现,但是失败了 pred(n) 是无法调用的,类型错误

img

使用 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)))
  }

  // λfx.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) 为了达到函数式中数学表达的“正确性”而所做的“约束”.个人理解,如果一个语言本身就是一个纯函数式语言,它是不用去强调不可变的数据操作的,其实都是一些“符号”,做的事情是在一些既定规则下,做符号的替换.

...全文
372 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
SoftwareTeacher 2022-11-26
精选
  • 打赏
  • 举报
回复
10.00元

鼓励一下交作业的好学生! 请看红包!

Thankssunhine 2022-11-27
  • 举报
回复
@SoftwareTeacher 很不好意思的,作业拖了很久才能交上.

230

社区成员

发帖
与我相关
我的任务
社区描述
日程:https://bbs.csdn.net/topics/608593392 主页:https://bobzhang.github.io/courses/ B站: “张宏波的基础软件课程”
rescript开发语言 个人社区 广东省·深圳市
社区管理员
  • raelidea
  • MoonBit月兔
  • 幻灰龙
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧