函数式编程笔记(一)——粮草先行

1.背景

希尔伯特的第十个问题,就是不定方程(又称为丢番图方程)的可解答性。随后哥德尔不完备定理指出,对于形式化系统,存在既不能证真,也不能证伪的问题。那么哪些问题是可判定,或者说可计算的呢?Turing 和 Church 分别推出了两种不同的模型来解决可计算问题。
  • Church 提出 lambda 演算,并通过这一系统定义了可计算函数的符号表示,有lisp硬件实现,函数式语言,如
    ML,Lisp,Haskell等则是以 lambda 演算为基础。

  • Turing 提出了图灵机模型,依据此模型,冯诺依曼型计算机被创造。指令式语言,如 Fortran,Pascal等都是以图灵机为基础,他们都依赖于状态序列。

后来证明,lambda演算与图灵机是等价的,能用图灵机器计算的东西lambda也可以完成。邱奇(Church)是图灵(Turing)的老师,lambda演算将所有一切看作函数,如有支持该演算的语言实现,是不是数学能证明的东西直接可以编程了呢,答案估计是这个学术用的语言还无法完全胜任实际应用,比如对于系统边界交互的IO,修改外部状态是不可避免的。于是图灵的状态机物理实现完成了反杀。

2.lambda简介

  • 让我们先从数学开始说起。函数是数学中一个非常基本的概念,我们很容易就能写出一个计算平方和的函数如下:

    f(x,y)=x*x+y*y

    上面这个平方和函数有个名字叫 f,有名字的好处是能方便的表示后续的计算,比如: f(3,4) = 3×3+4×4 = 25.(1)
    但再想想,名字对一个函数来讲是必须的么?当然不是,下面这个映射也表示了平方和函数: (x,y)->x*x+y*y (2)
    整体当成函数的名字,同样可以表示计算平方和过程: ((x,y)->x*x+y*y)(3,4) = 3×3+4×4 = 25. 我们把
    (2) 称为匿名函数,它有两个参数。那么再问,单参数函数和多参数函数的区分有必要吗? 可以换个角度来看,我们把 (2)
    改写成下面这个样子: x->(y-> x*x+y*y). (3) 这个映射是什么意思呢?x被映射成了y-> x*x+y*y ,后者是一个以
    y为参数的函数。换句话说, (3)表示的是把一个数映射成函数的函数,这就是所谓的“高阶函数(”
    High-OrderFunction)了。注意, (3) 现在是个单参数的函数,我们可以把它先应用于参数 3,得到一个新的函数:
    (x->(y-> x*x+y*y))(3) = y-> 3×3+y*y = y->9+y*y. 再把这个函数应用于参数 4:
    (y->9+y*y)(4) = 9+4×4 = 25. 这样就清楚了,(3)
    同样可以进行平方和的计算。用类似的方法,可以把任意多参数函数都转换成单
    参数的高阶函数,这个转换又叫做柯里化(Currying),这是以数学家 Haskell Brooks Curry 命名的。
    匿名函数和柯里化,是 lambda演算为简化函数概念而采取的方法

  • lambda项是一种形式语言,换句话说,就是一类特殊形式的字符串罢了,没有任何内在的意义,只是个
    “形式”。通常情况下,当讨论一个形式语言的时候,我们需要用另一种元语言来指称形式语言里的元 素。就如讨论自然数时,我们经常说
    “对任意自然数 n”,这里的 n本身并不是自然数,用来指称自然数 罢了。我们也需要一些 “n” 来表示 lambda 项中的元素。
    Alpha 替换

    我们可以在命名不冲突的情况下,把表达式中绑定的标识符替换为其他标识符。

    (λz.z) ≡ (λy.y) ≡ (λt.t) ≡ (λu.u)

    Beta 化简

    在函数应用中,我们用输入的表达式代入到函数体中绑定的标识符上,这一过程称为 Beta 化简。

    (λx.xy)z ≡ zy 闭包 在函数抽象中,如果函数体中存在的标识符存在于头部, 称为绑定标识符(bound
    variables)。在函数应用中,输入会被代换到绑定的标识符上。 不存在于头部的,称为自由标识符(free variables)。
    比如下面这个表达式中,x 为 绑定变量,y为自由变量 λx.xy
    此时,x作用域为有界,或者说闭包的,y是自由变量,必须从环境上下文中得到它的值

  • 以上两条规则举例说明如下(开始的平方和例子): λ x y.x*x+y*y
    =λ x.(λ y.x*x+y*y)(科里化,xy两个参数化为单参数)
    =λ a.(λ b.a*a+b*b)(α替换,就是变量名不重要,换成啥都行)
    =(λa.(λb.a*a+b*b)4)(β 化简,就是将b的值换成4)
    =(λa.a*a+4*4)3
    =(3*3+4*4)=25

在λ演算里面,所有的事物都是函数演算得出的,布尔值,自然数等等
T = λ x y. x(输入两个参数选其中一个)
F = λ x y. y(输入两个参数选其中另一个)
以上有没有if-else的味道呢,因为λ可以作用自身,因此递归可以实现循环。
0 = λ f x. x(f作用x零次,如果f返回0++的函数是不是就实现了呢)
1 = λ f x. f x(f作用x一次)
2 = λ f x. f (f x)(f作用x 2次)
继而演算出逻辑运算and or not …还有算术运算…(于是,我们拿这些函数开始码自己的函数了)

3.图灵机

图灵机,又称图灵计算、图灵计算机,是由数学家阿兰·麦席森·图灵(1912~1954)提出的一种抽象计算模型,即将人们使用纸笔进行数学运算的过程进行抽象,由一个虚拟的机器替代人们进行数学运算。
所谓的图灵机就是指一个抽象的机器,它有一条无限长的纸带,纸带分成了一个一个的小方格,每个方格有不同的颜色。有一个机器头在纸带上移来移去。机器头有一组内部状态,还有一些固定的程序。在每个时刻,机器头都要从当前纸带上读入一个方格信息,然后结合自己的内部状态查找程序表,根据程序输出信息到纸带方格上,并转换自己的内部状态,然后进行移动。

  • 这个无限长的纸带就是我们的程序。机器头里面就是指令。解释器通过纸带输入经过运算后输出到外部的纸带。

4.函数式编程

  1. 函数式编程是邱奇思想的在现实世界中的实现。不过不是全部的lambda演算思想都可以运用到实际中,因lambda演算在设计的时候就不是为了在各种现实世界中的限制下工作的。所以,就像面向对象的编程思想一样,函数式编程只是一系列想法,而不是一套严苛的规定。有很多支持函数式编程的程序语言,它们之间的具体设计都不完全一样。
  2. 编程语言生态系统的气候正在变化。程序员越来越多地要处理所谓的大数据,并希望利用多核计算机或计算集群来有效地处理。这意味着需要使用并行处理。
  3. 编程实战中,你是无法用Java语言以纯粹的函数式来完成一个程序的。比如,Java的I/O模型就包含了带副作用的方法(文件中读取的每一行,通常情况两次调用的结果完全不同)。不过,你还是有可能为你系统的核心组件编写接近纯粹函数式的实现。
  4. 引用透明性:“没有可感知的副作用”(不改变对调用者可见的变量、不进行I/O、不抛出异常)的这些限制都隐含着引用透明性。如果一个函数只要传递同样的参数值,总是返回同样的结果,那这个函数就是引用透明的。就好像牛顿第一定律,物体不受外力时处于平衡态(静止或匀速直线),但是现实中受到外力为零也可以是平衡态。只要修改对调用者透明就可以。换句话说,函数无论在何处、何时调用,如果使用同样的输入总能持续地得到相同的结果,就具备了函数式的特征。引用透明性是理解程序的一个重要属性。它还包含了对代价昂贵或者需长时间计算才能得到结果的变量值的优化(通过保存机制而不是重复计算),我们通常将其称为记忆化或者缓存。
  5. 递归和迭代:纯粹的函数式编程语言通常不包含像while或者for这样的迭代构造器。为什么呢?因为这种类型诱使你修改对象。比如,while循环中,循环的条件需要更新;否则循环就一次都不会执行,要么就进入无限循环的状态。但是,很多情况下循环还是非常有用的。我们在前面的介绍中已经声明过,如果没有人能感知的话,函数式也允许进行变更,这意味着我们可以修改局部变量。遵守“引用透明性”原则的函数,其计算结构可以进行缓存。从长远看,减少共享的可变数据结构能帮助你降低维护和调试程序的代价。采用递归可以取得迭代式的结构,比如while循环。尾递归优化可避免栈溢出。
  6. 高阶函数:函数式编程的世界里,能满足下面任一要求就 可以被称为高阶函数(higher-order function):
    •  接受至少一个函数作为参数
    •  返回的结果是一个函数
      高阶函数接受至少一个或者多个函数作为输入参数,或者返回另一个函数的函数。
  7. 科里化:是一种将具备2个参数(比如,x和y)的函数f转化为使用一个参数的函数g,并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。后者的返回值和初始函数的 返回值相同,即f(x,y) =(g(x))(y)。一个函数使用所有参数仅有部分被传递时,通常我们说这个函数是部分应用的(partially applied)。一等函数是可以作为参数传递,可以作为结果返回,同时还能存储在数据结构中的函数。  科里化是一种帮助你模块化函数和重用代码的技术。
  8.  模式匹配:是一种函数式的特性,它能帮助你解包数据类型。它可以看成Java语言中switch语句的一种泛化。
  9. 闭包:“闭包”一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。一般而言,函数返回时栈上的数据都会丢失,闭包应该是在堆里。closure将函数编程与面向对象的方法结合了起来。它能在运行时从相应的域中获得变量,从而可以把该变量当成“成员变量”来访问,也因为这样,就不再需要去创建一个成员变量了。
  10. 行为参数化:就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。传递代码,就是将新行为作为参数传递给方法。在面向对象编程里,此部分可以通过继承实现行为的多态。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值