中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

函數(shù)式編程中的 “函數(shù)們”

2018-07-20    來源:編程學習網(wǎng)

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

函數(shù)式編程中函數(shù)有三種不同的解讀方式,分別為純函數(shù)、高階函數(shù)和一等函數(shù)。本文分別對這三者的概念、應用和聯(lián)系進行詳解。

純函數(shù)

定義:

1. 相同的輸入必定產(chǎn)生相同的輸出

2. 在計算的過程中,不會產(chǎn)生副作用

滿足上述兩個條件,我們就說該函數(shù)是純函數(shù)。

純函數(shù)也即數(shù)學意義上的函數(shù),表達的是數(shù)據(jù)之間的轉換(映射)關系,而非計算步驟的詳述。數(shù)學函數(shù)的定義:

函數(shù)通常由定義域 X 、值域 Y, 以及定義域到值域的映射 ff: X -> Y )組成。

純函數(shù)讓我們對寫出的函數(shù)具有完全的控制能力。純函數(shù)的結果 必須 只依賴于輸入?yún)?shù),不受外部環(huán)境的影響;同時純函數(shù)在計算結果的過程中,也不會影響(污染)外部環(huán)境,即不會產(chǎn)生副作用。

函數(shù)組合

純函數(shù)定義中的兩個條件保證了它(的計算過程)與外界是完全隔離,這也是函數(shù)組合的基礎。

只有函數(shù)組合中的所有函數(shù)都是純函數(shù),我們組合起來的新函數(shù)才會是純函數(shù)。我們可以對使用純函數(shù)組合出來的新函數(shù)從數(shù)學上證明(推導)其正確性,而無需借助大量的單元測試。

只要在函數(shù)組合時引入一個非純函數(shù),整個組合出來的函數(shù)將淪為非純函數(shù)。如果將函數(shù)組合比作管道的拼接,只要組成管道的任何一小節(jié)有泄露或者外部注入,我們便失去了對整條管道的完全控制。

要想實現(xiàn)函數(shù)組合,還需要滿足連續(xù)性,描述如下:

因為純函數(shù)可以看作定義域到值域映射,待組合的函數(shù)中,上一個函數(shù)的值域須等于下一個函數(shù)的定義域,也即上一個函數(shù)的輸出(類型)等于下一個的輸入(類型)。

假設有兩個函數(shù): f: X -> Yg: Y -> Z ,只有 codomain(f) = domain(g) 時, fg 才可以組合。

引用透明及緩存

在不改變整個程序行為的情況下,如果能將其中的一段代碼替換為其執(zhí)行的結果,我們就說這段代碼是引用透明的。

因此,執(zhí)行一段引用透明的代碼(函數(shù)),對于相同的參數(shù),總是給出相同的結果。我們也稱這樣的函數(shù)(代碼)為純函數(shù)。

引用透明的一個典型應用即函數(shù)緩存。我們可以將已經(jīng)執(zhí)行過的函數(shù)輸入值緩存起來,下次調用時,若輸入值相同,直接跳過計算過程,用緩存結果代替計算結果返回即可。

函數(shù)緩存的實現(xiàn)依賴于閉包,而閉包的實現(xiàn)又依賴于高階函數(shù),高階函數(shù)的實現(xiàn)又依賴于一等函數(shù)。我們按照這條依賴鏈,從里往外依次對它們進行講解。

一等函數(shù)(First Class Functions)

程序語言會對基本元素的使用方式進行限制,帶有最少限制的元素被稱為一等公民,其擁有的 “權利” 如下:

  • 可以使用變量命名;
  • 可以提供給函數(shù)作為參數(shù);
  • 可以由函數(shù)作為結果返回;
  • 可以包含在數(shù)據(jù)結構中;

乍一看,我們應該首先會想到程序中的基本數(shù)據(jù)結構(如 number、array、object 等)是一等公民。如果函數(shù)也被視為一等公民,我們便可以像使用普通數(shù)據(jù)一樣對其使用變量命名,作為參數(shù)或返回值使用,或者將其包含在數(shù)據(jù)結構中。在這里函數(shù)和數(shù)據(jù)的邊界開始變得不再那么分明了。函數(shù)被視為一等公民后,其能力和適用范圍被大大擴展了。

下面使用 JavaScript 對上面第一條和第四條 “權利” 進行講解。第二、三條與高階函數(shù)密切相關,將放到下一節(jié)的高階函數(shù)中講解。

使用變量命名

const square = x => x * x

上面代碼定義了一個求平方值的函數(shù),并將其賦給了 square 變量。

可以包含在數(shù)據(jù)結構中

Ramda 中有一個API:evolve ,其接受的首個參數(shù)便是一個屬性值為函數(shù)的對象。evolve 函數(shù)會遞歸地對 “待處理對象” 的屬性進行變換,變換的方式由 transformation 內置函數(shù)屬性值的對象定義。示例如下(示例中的 R.xxx 都是 Ramda 中的API,相關API的功能可以參考Ramda 文檔):

var tomato  = {name: 'Tomato', data: {elapsed: 100, remaining: 1400}, id:123};
var transformations = {
  name: R.toUpper,
  data: {elapsed: R.add(1), remaining: R.add(-1)}
};

R.evolve(transformations)(tomato);
//=> {name: 'TOMATO', data: {elapsed: 101, remaining: 1399}, id:123}

高階函數(shù)

定義:

使用函數(shù)作為輸入?yún)?shù),或者返回結果為函數(shù)的函數(shù),被稱為高階函數(shù)。

作為參數(shù)或返回值的函數(shù),是一等函數(shù)的應用之一。高階函數(shù)以一等函數(shù)作為基礎,只有支持一等函數(shù)的語言才能進行高階函數(shù)編程。

以熟悉的 filter 函數(shù)為例,我們可以用 filter 對列表中的元素進行過濾,篩選出符合條件的元素。filter 的類型簽名和示例代碼如下:

filter :: (a → Boolean) → [a] → [a]
const isEven = n => n % 2 === 0;

const filterEven = R.filter(isEven);

filterEven([1, 2, 3, 4]); //=> [2, 4]

filter 接受一個判斷函數(shù)(判斷輸入值是否為偶數(shù)) isEven ,返回一個過濾出偶數(shù)的函數(shù) filterEven 。

閉包

定義:

閉包是由函數(shù)及該函數(shù)捕獲的其上下文中的自由變量組成的記錄

舉例講:

function add(x) {
  const xIn = x;
  return function addInner(y) {
    return xIn + y;
  }
}
const inc = add(1);
inc(8); //=> 9;

const plus2 = add(2);
plus2(8); //=> 10;

上述代碼中返回的函數(shù) addInner 及由其捕獲的在其上下文中定義的自由變量 xIn ,便組成了一個閉包。

上述代碼中最外層的 add 函數(shù)是一個高階函數(shù),其返回值為一等函數(shù) addInner 。

其實 add 函數(shù)的參數(shù) x 也是 addInner 上下文的一部分,所以 ‘xIn’ 也就沒有存在的必要了, add 代碼優(yōu)化如下:

function add(x) {
  return function addInner(y) {
    return x + y;
  }
}

借助于箭頭函數(shù),我們可以進一步優(yōu)化 add 的實現(xiàn):

const add = x => y => x + y

是不是非常簡潔?由此我們可以一窺函數(shù)式編程強大的表達能力。

閉包主要用來做數(shù)據(jù)緩存,而數(shù)據(jù)緩存應用非常廣泛:包括函數(shù)工廠模式、模擬擁有私有變量的對象、函數(shù)緩存、還有大名鼎鼎的柯里化。

其實上述代碼中 add 函數(shù)便是柯里化形式的函數(shù)。

上述代碼中的 const inc = add(1); 和 const plus2 = add(2); 是一種函數(shù)工廠模式,通過向 add 函數(shù)傳入不同的參數(shù),便會產(chǎn)生功能不同的函數(shù)。函數(shù)工廠可以提高函數(shù)的抽象和復用能力。

例如我們有一個如下形式的 Ajax 請求函數(shù):

const ajax = method => type => query => { ... };

const get = ajax('GET');
const post = ajax('POST');

const getJson = get('json');
const getHtml = ajax('GET')('text/html') = get('text/html');

我們抽象出了最一般的 ajax 請求函數(shù),在具體應用時,我們用能通過函數(shù)工廠生產(chǎn)出作用不同的函數(shù)。

通過上面幾個小節(jié),我們講解純函數(shù)(數(shù)學意義上的函數(shù))、一等函數(shù)、高階函數(shù),還有閉包,下面通過集上述所有概念于一身的 函數(shù)緩存 ,來結束函數(shù)式編程中的 ”函數(shù)們“ 的論述。

函數(shù)緩存 memoize

函數(shù)實現(xiàn):

const memoize = pureFunc => {
  const cache = {};
  return function() {
    const argStr = JSON.stringify(arguments);
    cache[argStr] = cache[argStr] || pureFunc.apply(pureFunc, arguments);
    return cache[argStr];
  };
};

memoize 的功能是對傳入函數(shù) pureFunc 進行緩存,返回緩存版本的 pureFunc 。當我們使用參數(shù)調用緩存的函數(shù)時,緩存的函數(shù)會到 cache 中查找該參數(shù)是否被緩存過,如果有緩存,則不需要再次計算,直接返回已緩存值,否則對本次輸入的參數(shù)進行計算,緩存計算的結果以備后用,然后將結果返回。

memoize 只有對純函數(shù)的緩存才有意義。因為純函數(shù)是引用透明的,其輸出只依賴于輸入,并且計算過程不會影響外部環(huán)境。

舉一個極端的例子,假如我們有一個隨機數(shù)字生成函數(shù) random() , 如果對其進行了緩存:

const memoizedRandom = memoize(random);

memoizedRandom 除了第一次生成一個隨機值外,隨后的調用都返回第一次緩存的值,這樣就失去了 random 的意義。再假如,我們對終端字符輸入函數(shù) getchar() 進行了緩存,每次調用都會是第一次獲取的字母。

memoize 內部實現(xiàn)了一個閉包的創(chuàng)建。返回的緩存函數(shù)和自由變量 cache 共同構成了一個閉包。自由變量 cached 用于對已經(jīng)計算過的數(shù)據(jù)(參數(shù))的緩存。而閉包本身是由高階函數(shù)和一等函數(shù)實現(xiàn)的。

總結

本文對函數(shù)式編程中的 “函數(shù)們” 做了詳細解釋:純函數(shù)、一等函數(shù)、高階函數(shù),并展示了它們的應用。其中純函數(shù)是函數(shù)組合的基礎;一等函數(shù)是高階函數(shù)的實現(xiàn)基礎,一等函數(shù)和高階函數(shù)又是閉包的實現(xiàn)基礎。

最后通過函數(shù)緩存函數(shù) memoize 將純函數(shù)、一等函數(shù)、高階函數(shù)和閉包聯(lián)系起來,用函數(shù)式編程中的 “函數(shù)們” (函數(shù)式三鏢客)的一次 “聯(lián)合行動” 結束本文。

參考文檔

What is a Function? .

Functional Programming .

Referential Transparency .

 

來自:http://www.techug.com/post/functions-in-functional-programming.html

 

標簽: 代碼

版權申明:本站文章部分自網(wǎng)絡,如有侵權,請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:OAuth 2.0 理解

下一篇:分布式事務的總結與思考