advanced java (补二)lambda演算

advanced java (补二)lambda演算

谈到 lambda 演算一般来说就是比较偏学术界的东西了,一般来说,和编程语言理论 ( programming language theory,PLT) 息息相关,这一门学科用于编程语言的设计和实现。但是对于工程来讲还是有一些距离,不过适当了解一下历史,还是挺有意思的。

计算机语言最开始源于数学,数学家阿隆佐·邱奇在20世纪30年代提出了λ演算,然后将这个理论发扬光大的是 lisp 系的语言,其中如对函数的抽象,应用,递归等对语言有很大的的影响。通过函数可以定义语言的非形式化描述,可以用 λ 表达式表示现代语言,如 邱奇 α-等价 , β-归约 , η-变换

同样 邱奇数 把数据和运算符嵌入到lambda演算内,这个可以了解一下。

Y-组合子 允许匿名函数足够达成递归的作用,证明了 lambda 演算和计算机程序是等价的。

当然,这些知识可能对工程上作用不大,这些深入后是数学家们的范畴。不过对于有好奇心的人,还是有吸引力的。

非形式化描述

在λ演算中,每个表达式(λx.x)都代表一个函数,这个函数有一个参数(x),并且会返回一个值。因为可以科里化,因此都可以单个参数的形式,同时是左结合的。

α-等价

α-等价表达的是,被绑定变量的名称是不重要的。

也就是说 λx.x和 λy.y 是同一个函数,当然更复杂的情况 λx.(λx.x) x 和 λy.(λx.x) y 也是一样的。

def func1 = (x:Int) => 5 + x
def func2 = (y:Int) => 5 + y

也就是说 func1 和 func2 是同样的函数,虽然参数名不一样,但是是等价。(这里准确地说是 x => x,或者表达式应该是 λx.x 5,不过也没有影响)

参数名不重要,也就可以写成这样。

_ => 5 + _
β-归约

β-归约表示,在应用函数时,可以直接对结果中相应的变量进行替换。

定义是 (λx.E)M -> E[x:=M]

举个例子来说 (λx.λa.xa)(λz.zy) 可以规约成 (λa.λz.zya) , 其中 M 是 (λz.y),但是需要注意规约前后的作用域要相同

val y = 100
def lx_la_xa = (x:Int => Int) => (a:Int) =>  x andThen ( (t:Int) => a * t )
def lz_zy = (z:Int) => z + y
def func1 = lx_la_xa apply lz_zy
def func2 = (a:Int) => (z:Int) => a * ( z + y )

其中 func2 就是 func1 规约后的结果,将 lx_la_xa 中的变量 x 替换成了 lz_zy

η-变换

η-变换的外延性指的是对于任一给定的参数,当且仅当两个函数得到的结果都一致,则它们将被视同为一个函数。

对所有的 a 有 (λx .f x) a == f a,可得λx .f x == f ,即 η-归约

def f:Function1[Int,Int] = (t:Int) => 54 + t
def func1 = (x:Int) => {(f andThen ( t => t )).apply(x) }
println(f.apply(24) == func1.apply(24))

对于 apply 任意值都打印 true ,func1 和 f 是等价的

丘奇逻辑

丘奇编码把数据编码到lambda演算,可以用来表示自然数和布尔逻辑。

丘奇数
自然数 lambda表达式
0 λf.λx.x
1 λf.λx.f x
2 λf.λx.f (f x)
3 λf.λx.f (f (f x))

也就是说自然数表示高阶函数的阶级,加法也就是两个高阶函数的结合,自然也就是阶级相加,乘法也就是m加上n次,以及前缀( pred ),后继( succ ),指数和减法

丘奇布尔值
布尔逻辑 lambda表达式
true λa.λb.a
false λa.λb.b
and λp.λq.p q p
or λp.λq.p p q
if λp.λa.λb.p a b

布尔值可以参与与或运算。同理 not 和 xor 也可以实现,以及谓词是 ISZERO

Y-组合子

Y-组合子的定义如下:

Y = λf.(λx.(f(x x))(λx.(f(x x))))

也就是说匿名函数也是可递归的,证明了 Curry–Howard 同构。

用斐波那契数列来演示

def fib(n:Int): Long = {
  @annotation.tailrec
  def recursion(m:Int,frist:Long,last:Long): (Long,Long) = {
    if(m==1)
    (frist,last)
    else
    recursion(m-1,last,frist+last)
  }
  recursion(n,0,1)._2
}

将 recursion 改写成 arrow function 的形式

class Point (val m:Int,val frist:Int,val last:Int) {}
val fib20 = {
  def recursion:(Point => Point) = point => {
    if(point.m == 1)
      point
    else
      recursion.apply(new Point(point.m - 1 , point.last , point.frist + point.last))
  }
  recursion.apply(new Point(20,0,1))
}
println(fib20.last)

然后将函数作为参数传入

class PointWithFunc (val m:Int,val frist:Int,val last:Int,val f:PointWithFunc => PointWithFunc) {}
val fib20 = {
  def recursion:(PointWithFunc => PointWithFunc) = point => {
    if (point.m == 1)
      point
    else
      point.f .apply(new PointWithFunc(point.m - 1, point.last, point.frist + point.last, point.f))
  }

  recursion.apply(new PointWithFunc(20, 0, 1, ???))
}

显而易见,这里最后需要传入 f ,同时主参数无法传入 f,需要写一个新的函数

class PointWithFunc(val m: Int, val frist: Int, val last: Int, val f: PointWithFunc => PointWithFunc) {}
class Point(val m: Int, val frist: Int, val last: Int) {}
val fib20 = {
  def recursion:(PointWithFunc => PointWithFunc) = point => {
    if (point.m == 1)
      point
    else
      point.f .apply(new PointWithFunc(point.m - 1, point.last, point.frist + point.last, point.f))
  }

  def result:(Point => PointWithFunc) = point => {
    recursion.apply(new PointWithFunc(point.m, point.frist, point.last, recursion))
  }
  
  result.apply(new Point(20, 0, 1))
}
println(fib20.last)

通过引用透明法则,对函数进行替换并匿名

class PointWithFunc(val m: Int, val frist: Int, val last: Int, val f: PointWithFunc => PointWithFunc) {}
class Point(val m: Int, val frist: Int, val last: Int) {}
val fib20 = {
    {point:Point => {
    {point:PointWithFunc => {
      if (point.m == 1)
        point
      else
        point.f .apply(new PointWithFunc(point.m - 1, point.last, point.frist + point.last, point.f))
      }}.apply(new PointWithFunc(point.m, point.frist, point.last,
      {point:PointWithFunc => {
        if (point.m == 1)
          point
        else
          point.f .apply(new PointWithFunc(point.m - 1, point.last, point.frist + point.last, point.f))
      }}
    ))
  }}.apply(new Point(20, 0, 1))
}
println(fib20.last)

可以发现,函数都被匿名了,并且是递归的。