函數式編程 : 一個程序猿進化的故事

阿袁工做的第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。
  • 可使用卷積來方便於多步驟計算的要求。

參照

相關文章
相關標籤/搜索