從函數式編程提及

前言

「好事者」總結了一個份關於Android開源項目的榜單,榜單裏面包含了Android開發中經常使用的開源庫,排在第一的是網絡封裝框架Retrofit,而RxJava(RxAndroid)則排名十三。榜單是以使用優先級來評判的,Android開發中必不可少的模塊排名會高一些(使用多而選擇少,因此人人都會去用),例如網絡加載框架(Retrofit+okhttp),圖片加載框架(glide)等。html

可是若是讓我選擇一個對程序開發模式或者整個App架構影響最大的開源庫,我會堅決果斷的選擇RxJava;若是把開發App比喻成作飯,Retrofit或者okhttp充其量是蔥、蒜、姜,而RxJava則是主材;蔥、蒜、姜可以影響菜的口味,可是最終作出來菜是怎樣則是由主材決定的。java

榜單中把RxJava的關鍵字定義成「異步」,這是錯誤或者說是膚淺的。RxJava本質是一種函數式編程的實現框架,它從根本上改變了Android開發過程當中的思惟方式,下面來看看什麼是函數式編程。git

函數式編程是什麼?

要釐清函數式編程,咱們必須先明確幾個概念的區別:github

  • 面向對象編程\面向過程編程(編程思想)
  • 函數式編程\指令式編程(編程範式)

前者是編程思想,後者屬於編程範式,通常來講前者包含後者。首先須要明確一點:這四個概念不是對立的,他們之間都是互相關聯和重疊的;編程思想並非非此即彼,面向對象也有可能會用到面向過程的思想,面向過程也有可能會用到面向對象的思想。函數式編程和指令式編程也同樣,區別在因而否是大部分的思惟方式契合某種編程方式。算法

函數式編程很早的時候就已經出現了,像古老的Lisp語言就是函數式編程的典範,最近的火熱的kotlin語言也屬於函數式編程語言,看一個kotlin的函數編程例子:編程

/** * 找出list中某些單詞的使用頻率,從高到底輸出 */
private fun test(list : MutableList<String>):TreeMap<String, Int>{
        val words = mutableSetOf("the","abd","to")
        val wordMap = TreeMap<String, Int>()
        list.stream()
                .map { w->w.toLowerCase() }
                .filter { w->!words.contains(w) }
                .forEach{w->wordMap.put(w,wordMap.getOrDefault(w,0)+1}
        return wordMap;
    }

複製代碼

相比於傳統的Java語言編程方式,函數式編程更加註重「算法」,回憶大學學習算法課程的時候,老師教學過程當中的每每是使用僞代碼,好比找出A B C三個數中最大值。安全

Begin(算法開始)
輸入 A,B,C
IF A>B 則 A→Max
不然 B→Max
IF C>Max 則 C→Max
Print Max
End (算法結束)
複製代碼

而函數式編程語言可能相比更加精煉一些,bash

Begin(算法開始)
輸入 A,B,C
max -> Max(A,B)
max->Max(max,c)
Print max
End (算法結束)

複製代碼

函數式編程語言更加關注函數,函數在編程過程當中是「一等公民」,上面例子中,就像僞代碼同樣,代碼把整個問題域分解成粒度合適的小問題,經過解決一個個小問題,從而實現整個問題的求解,a->fun1->fun2->b。網絡

在指令式編程過程當中咱們須要對每個小問題進行代碼封裝求解,更加關注「問題域」的解決思路,忽略實現的細節。面向對象程序設計其實也是包含這種思想,花更多的時間關注更高層次問題的抽象,考慮解決複雜的業務場景。數據結構

在《函數式編程思惟》書上有一句原文:「假如語言不對外暴露那麼多出錯的可能性,那麼開發者就不那麼容易犯錯」,對於這句話個人理解是:

  • 儘量的對基礎函數作封裝,好比對基本數據結構的操做,讓調用者儘量的具備肯定性的輸入和輸出,好比上面例子中對集合的操做map、filter等等;
  • 儘量讓可變的東西變成不可變,例如變量、多線程、集合等等;

前面這兩個結論就隱含着函數式編程語言中巨大的設計思路,函數式編程中函數指的是數學概念上的函數f(x),函數的值僅決定於函數參數的值,不依賴其餘狀態,在純粹的函數式編程語言中必須聽從兩個規範:

  • 不可變性
  • 對基礎函數(高階)封裝

爲這兩條就是函數式編程語言的本質特徵。

函數式編程語言的不可變性

說到函數式編程的不可變特性,咱們必須先說一說指令式編程的「可變性」,在傳統的Java語言中,咱們時刻須要保存一些計算「狀態」信息,這些多是一些具體的數據、引用指向等等信息,好比咱們要求解0-10的和。

public void sum(){
    int sum = 0;
    for(int i=0;i<10;i++){
        sum += i;
    }
    return sum;
}
複製代碼

須要不斷對sum賦值,sum這個變量一直在不斷的被重用,所以咱們說sum是可變的。

這樣會帶來什麼後果?假設sum這個值是一個對象成員變量,若是該對象被多個線程調用,sum值就會處於一種線程不安全的狀態,假設sum一個引用類型的變量,通過多個函數的調用引用類型會不斷被賦值,最終會致使輸出的結果是不可預見的,總結不可變帶來的一些問題:

  • 線程不安全
  • 應用不透明
  • 資源被競爭
  • 可測性降低

那在Java中有沒有不可變特性的體現呢?String類就是最明顯的不可變的實現,String類的實現具體如下幾個特性:

  • 聲明成 final,包括某些關鍵的成員變量、類的聲明等等
  • 無參數的構造函數

String類的實現本質就是爲了「安全的賦值」,當咱們定義一個字符串,而且從新賦值,它並不是是在同一個內存引用上修改數據,而是從新生成一個新的引用

String a= "1122";
a = "3333";
複製代碼

image

那麼在函數式編程語言中是怎麼實現的「不變性」的呢,通常從如下幾個方面進行考慮:

  • 語法層面進行約束和規範。例如kotlin的val不可變變量,可變集合和不可變集合的規範等等;
  • 利用函數進行參數的傳遞。函數的輸入固定,輸出也是固定的,因此就保證了函數式參數的不變性;
  • 善用遞歸等語言特性;
  • 使用高階函數;

上面的求和例子,在kotlin函數式語言中,實現的方式:

fun sum() : Double{
        val a = 9 // val的不可變量
        // 高階函數進行數據求和
        return Array<Int>(a,{ w->w+1}).sumByDouble({w->w.toDouble()})
    }
複製代碼

能夠看到,kotlin貫徹執行了函數式語言的不可變特性,經過高階函數來對整個過程當中產生的臨時變量所有轉換成函數參數,相似於f(g(x))。

函數式編程語言之因此會流行起來很大緣由是由於「不變性」,隨着摩爾定律在計算機上面的失效,致使計算機的性能提高更多依賴於多核計算,而多核計算的最大問題在於「併發」的處理,函數式語言經過避免賦值產生的「不變性」偏偏契合了多核計算機的發展。

lambda表達式和高階函數

lambda表達式在函數式編程語言中每每體如今「匿名函數」上。當你須要一個函數,可是你又不想要給這個一次性函數取一個「不須要」的名字,這個時候匿名函數就能夠派上用場了。在Android中匿名函數隨處可見,例如設置一個監聽:

mCompleteBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doSomeThink(v)
            }
        });

複製代碼

在Java中這種對匿名函數的寫法是極其不優雅和麻煩的,經過lambda表達式去簡化這段代碼,只保留參數和對應的方法實現:

mCompleteBtn.setOnClickListener({v->doSomeThink(v)})
複製代碼

咱們把

{v->doSomeThink(v)}
複製代碼

這段代碼塊稱之爲lambda表達式,很明顯lambda表達式最大的做用就是:

==讓代碼更簡潔==

在多個參數的時候lambda表達式的寫法相似

(x,y)->x^2+y^2
複製代碼

高階函數其實就是利用lambda表達式做爲參數或者參數返回值的函數,例如:

val items = listOf(1, 2, 3, 4, 5)
 items.map ({t->t*2})
複製代碼

其中map就是高階函數,

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
複製代碼

固然任何高階函數本質上都是指令式編程的封裝,函數式編程會對一些經常使用的數據結構和集合(set map)作一些函數式封裝。

總結

編程範式理論上是沒有所謂的好壞之分,只有合適與否。某種意義上來講,函數式編程和指令式編程在底層底層實現並無本質上的區別,只不過函數式編程經過一系列的規範來保證編程過程當中儘量的遵循「不可變性」,經過對基本數據結構和集合提供足夠多的高階函數,從而讓編程者更加關注解決問題的步驟而非具體的實現。

參考文章

  • https://www.ibm.com/developerworks/cn/java/j-ft20/index.html
  • http://www.ruanyifeng.com/blog/2012/04/functional_programming.html
相關文章
相關標籤/搜索