函数式编程 : 一个程序猿进化的故事

阿袁工做的第1天: 函数式编程的历史

阿袁中午和阿静一块儿吃午饭。阿袁提及他最近看的《艾伦·图灵传 如谜的解谜者》。
因为阿袁最近在学习Scala,因此关注了一下图灵传中关于函数式编程的一些历史。
关于函数式编程的故事,能够从1928年开始讲起:希尔伯特在当年的一个大会上,提出了他的问题:程序员

  • 第一,数学是完备的吗?
    是否是每一个命题都能证实或证伪。
  • 第二,数学是相容的吗?
    永远不会推出矛盾的命题?
  • 第三,可断定性问题:数学是可断定的吗?
    是否存在一个算法,能够应用于任何命题,而后自动给出该命题的真假?

希尔伯特的哲学企图是:每一个问题的答案都将会是“是”。我想这个信念来自于对数学的神圣信仰。算法

不幸的是,在这同一个大会上,第一个问题就被否认了。一个年轻的捷克数学家,柯特·哥德尔的宣布,可以证实,
算术必定是不完备的:存在既不能证实,也不能证伪的命题。express

注:欧几里得几何的五大公理并非一个反例。欧几里得几何能够被一阶公理化为一个完备的系统。
(这句话啥意思?)个人理解是:公理是一个定义,或者说是不证自明的。编程

随后,哥德尔不完备定理的第二定理又否认了第二个命题:“数学是相容的吗?”app

对于第三问题(可断定性问题),在1936年,丘奇(Alonzo Church)和艾伦·图灵分别证实了存在不可解的问题。
图灵提出的图灵机模型,而丘奇提出了一个基于lambda演算(lambda calculus)的模型,这两个模型被图灵证实是等价的。
图灵在图灵机的思想上,继续思考,逐步设计出早期的计算机(一个英国版的计算机,比冯诺依曼的计算机更早被建造出来。冯诺依曼对图灵机也是承认的。),而且考虑人工智能的问题。
而lambda演算概念,则被发扬光大成了函数式编程思想。
伟大的数学!ide

函数式编程是基于表达式(expression)的一种编程范式。
函数式编程将计算视为对数学函数的求值过程。函数式编程

  • 函数式编程是:
    • 声明式编程(declarative programming),其含义是基于表达式(expression)。
    • 基于表达式的含义:表达式是用来求值的。
    • 倾向避免使用可变数据。
  • 面向对象编程的特色:
    • 命令式编程(imperative programming),其含义是基于陈述(statement)。
    • 基于陈述的含义:语句是用来执行的。

阿袁工做的第2天: 函数式编程:告别对象,迎接函数

阿袁和阿静中午又在一块儿,继续讨论函数式编程。
“我认为,咱们能够把函数式编程理解成在作数学计算。这种编程风格是一种面向表达式(expression-oriented)风格。”,阿静慢慢地说道。
"我也是这么想的。因此,做为一个面向对象的程序员,咱们先要把对象的概念舍去掉。"
“是啊,倒空一些,才能学习到新的知识。”
“咱们怎么考虑class的做用呢?”
“在面向对象中,class的一个主要做用的封装。”
“那么,在函数式编程中,class的做用应该是对算法(函数)的分类了。”
“正解!咱们作一个游戏,看看若是把一个面向对象的程序,变成面向表达式的程序。”
“好啊,我先用Scala写一个面向对象的例子。”函数

// 这个例子的主要功能是对一个List排序。
// 这是一个基于面向对象思想的实现。
object Main {

    // 一个支持排序的class。
    // 这个class,须要外部提供一个比较器。
    class ListSorter[T](a: List[T]) {
        def data: List[T] = a
        def sort(comparer: IComparer[T]): List[T] = {
            return data.sortWith(comparer.compare)
        }
    }

    // 咱们为比较器定义一个interface,带一个比较函数compare。
    trait IComparer[T]{
        def compare(a: T, b: T): Boolean
    }

    // 这个一个具体的比较器,实现了比较器IComparer。
    class IntComparer extends IComparer[Int] {
        override def compare(a: Int, b: Int): Boolean = {
            return a < b
        }
    }
    
    // 测试一下
    def main(args: Array[String]): Unit = {
        val list = List(9,1,6,3,5)
        val sorter = new ListSorter[Int](list)
        // 在调用sort方法时,传入一个具体比较器对象。
        println(sorter.sort(new IntComparer()))
    }
}

在这个例子中,ListSorter须要外部提供一个比较方法。为了解决这个问题,面向对象的思路是:学习

  • 对外部功能,定义了一个接口。并在接口中,声明这个比较函数。
  • ListSorter的sort函数,经过接口来使用外部的比较方法。
  • 外部:定义了一个具体类,实现了这个接口。
  • 调用者:在调用ListSorter的sort函数时,传入一个具体类的对象。

“如今,咱们的任务就是:把这个例子改为面向表达式的风格。”
“首先,把sort函数的输入参数comparer变成一个函数类型。”
“这样,咱们就不须要IComparer,这个接口了。”
“IntComparer就能够从一个封装类,变成一个带比较函数的静态类。”测试

函数式编程的第一个例子:

// 这个例子的主要功能是对一个List排序。
// 这是一个基于面向表达式的实现。
object Main {
    // 一个支持排序的class。
    // 这个class,须要外部提供一个比较函数。
    class ListSorter[T](a: List[T]) {
        def data: List[T] = a
        def sort(f: (T, T) => Boolean): List[T] = {
            return data.sortWith(f)
        }
    }

    // 实现了一个比较函数。
    object IntComparer {
        def compare(a: Int, b: Int): Boolean = {
            return a < b
        }
    }

    def main(args: Array[String]): Unit = {
        val list = List(9,1,6,3,5)
        val sorter = new ListSorter[Int](list)

        // use function rather than object
        println(sorter.sort(IntComparer.compare))
        // use function with lambda expression
        println(sorter.sort( (a, b) => a < b ))
        // use function with underscore
        println(sorter.sort( _ < _ ))

        // fluent infix style
        println(sorter sort IntComparer.compare)
        // fluent infix style with lambda expression
        println(sorter sort {(a, b) => a < b})
        // fluent infix style with underscore
        println(sorter sort { _ < _ })
    }
}

注:这里面实现了多种风格。
lambda expression,能够当作匿名函数的实现方法。
underscore: underscore在scala中有多种含义。这里是一种匿名函数的实现,scala会根据上下文推测"_"的含义。
infix style: 能够看出,不须要"."了。

“太好了,咱们向函数式编程迈出了第一步!”

阿袁工做的第3天: 函数式编程:再纯粹一些

“在昨天的例子中,咱们仍是实例化了ListSorter。”
“是啊,按照函数式编程的思想,咱们须要把ListSorter的sort方法当作一个函数。”
“另外,我还学到了一点,在面向表达式风格中,不要写return。最后一条expression的结果就应该是函数的返回值。”
“嗯,好的,咱们继续改改看。”
函数式编程的改进版:

// 这个例子的主要功能是对一个List排序。
// 这是一个基于面向表达式的实现。
// * Changed ListSorter as module
// * Do not use return
object Main {
    object ListSorter {
        def sort[T](a: List[T], f: (T, T) => Boolean): List[T] = {
            // Do not use return
            a.sortWith(f)
        }
    }

    object IntComparer {
        def compare(a: Int, b: Int): Boolean = {
            // Do not use return
            a < b
        }
    }

    def main(args: Array[String]): Unit = {
        val list = List(9,1,6,3,5)
        // use function rather than object
        println(ListSorter.sort(list, IntComparer.compare))
        // use function with lambda
        println(ListSorter.sort[Int](list, (a, b) => a < b))
        // use function with underscore
        println(ListSorter.sort[Int](list, _ < _))
    }
}

发现了吗? fluent infix style没有了。这是由于,infix操做支持有一个参数的函数。

阿袁工做的第4天: 函数式编程:卷积(currying)

“fluent infix style有点接近人类的语言,使用好的话,能够增长可读性。”
"可是,它也有个限制,只支持有一个参数的函数。"
“其实,卷积能够解决这个问题。"
"卷积?"
“给你举个例子。通常的函数是这样的。”

def normalFunc(a: Int, b: Int, c:Int): Int = {
        a + b + c
    }

"而卷积函数变成这样,参数被分隔一个一个的。"

def curriedFunc(a: Int)(b: Int)(c:Int): Int = {
        a + b + c
    }

"卷积的思想是: 每次只给函数的一个参数赋值。这样的一个主要用途是:局部函数(partial function application),
能够想象为把一个计算分红多个步骤计算(multiple stage computation)。这是调用的方法:"

// Usage: Currying in partial function application
        val add2OneByOne = curriedFunc(1) _
        // call a curried function variable with a normal arugment
        println(add2OneByOne(2)(3))
        // output: 6

“卷积带来的一个附加益处,就是支持了多参数函数的infix操做。”

// Usage: call a curried function with an expression in fluent infix style
        println(curriedFunc {1} {2} {3})
        // output: 6

“scala真强大啊!咱们继续改改看。”

// 这个例子的主要功能是对一个List排序。
// 这是一个基于面向表达式的实现。
// * Using currying
object Main {
    object ListSorter {
        // curried function (a)(b)
        def sort[T](a: List[T])(f: (T, T) => Boolean): List[T] = {
            a.sortWith(f)
        }
    }

    object IntComparer {
        def compare(a: Int, b: Int): Boolean = {
            a < b
        }
    }

    def main(args: Array[String]): Unit = {
        val list = List(9,1,6,3,5)
        // use function rather than object
        println(ListSorter.sort(list)(IntComparer.compare))
        // use function with lambda expression
        println(ListSorter.sort(list)((a, b) => a < b ))
        // use function with underscore
        println(ListSorter.sort(list)(_ < _ ))

        // fluent infix style
        println(ListSorter.sort {list} {IntComparer.compare})
        // fluent infix style with lambda expression
        println(ListSorter.sort {list} {(a, b) => a < b})
        // fluent infix style with underscore
        println(ListSorter.sort {list} { _ < _ })

        // currying usage: partial function application
        val sortWith = ListSorter.sort(list) _
        // fluent infix style
        println(sortWith(IntComparer.compare))
        // fluent infix style with lambda expression
        println(sortWith {(a, b) => a < b})
        // fluent infix style with underscore
        println(sortWith { _ < _ })
    }
}

阿袁工做的第5天: 函数式编程:如何处理null

"今天有个新的认识。在面向对象语言中,咱们常用null。可是在数学计算中,null是没有意义的。"
“那么要使用什么呢?”
“若是返回值类型是一个集合,最好返回空集合。”
“若是返回值类型是一个值,scala提供了一个Option的泛型类,提供了一个None对象,表示返回的值是没有值。”
“代码示例以下。”

// 这个例子的主要功能是说明使用Nil和None、
object Main {
    object NilNoneSample {
        // 使用空集合。不要使用null。
        def getEmptyList(): Seq[Int] = {
            Nil
        }
        
        // 使用空集合。不要使用null。
        def getEmptyVector(): Vector[Int] = {
            Vector()
        }
        
        // 对于可能返回“没有值”的结果,使用Option泛型类。
        def getValueIfLargeThanZero(a: Int): Option[Int] = {
            if (a > 0) Option(a) else None
        }
    }

    def main(args: Array[String]): Unit = {
        // use empty collection replace null
        println(NilNoneSample.getEmptyList)
        // output: List()
        println(NilNoneSample.getEmptyVector)
        // output: Vector()

        // use None replace null
        println(NilNoneSample.getValueIfLargeThanZero(1).get)
        // output: 1
        println(NilNoneSample.getValueIfLargeThanZero(-1).isEmpty)
        // output: true
    }
}

阿袁的日记

2016年9月X日 星期六
最近和阿静接触的机会多了不少。也学会了一些函数式编程的概念。
总结一下:
函数式编程的风格,即面向表达式编程风格,有以下要求:

  • 把类看是算法的分类。
  • 使用函数代替对象。
  • 对于变量和参数,尽可能使用:值(最好是不变的),Collection和函数等类型。
  • 尽可能使用不可变的数据类型。(重申一遍)
  • 避免使用return语句。
  • 对于集合类型,使用空集合来代替null。
  • 对于其余数据类型,使用None代替null。
  • 可使用卷积来方便于多步骤计算的要求。

参照

相关文章
相关标签/搜索