過程或函式的副作用是 函式式程式設計主食

2021-10-11 23:09:16 字數 4527 閱讀 5900

函式式程式設計是什麼?

在函式式程式設計中,函式就是乙個管道(pipe)。這頭進去乙個值,那頭就會出來乙個新的值,沒有其他作用。——阮一峰 函式式程式設計入門教程

函式式程式設計本質上是一種數**算。因為是數**算所以自然就會涉及到加減乘除等運算和交換律結合律同一律分配律等運算法則。如果要函式順利的進行數**算,就要求函式必須是純的,不能有***,即純函式。但如果只是簡單的將純函式用於複雜的加減乘除運算,則會寫出一堆看起來雜亂無章的、不符合人類閱讀習慣和編碼直覺的**。因此函式式程式設計需要借助組合函式、柯里化、遞迴、閉包和各種各樣的高階函式讓**看起來更符合人類的直覺和邏輯思維的方式。

當我們在說函式是「一等公民」的時候,不要想當然的以為函式就是 js 世界裡的老大了,我們實際上說的是它們和其他物件都一樣:就是普通公民。

作為一等公民,函式可以被賦值給另外乙個變數,然而程式設計中卻有很多這樣的神奇操作:

const hi = name => `hi, $`

const greeting = name => hi(name)

實際上呼叫hi('girl')greeting('gril')的結果無論如何都是完全一樣的,greeting函式所作的不過是呼叫並返回hi函式而已。但實際上完全沒必要如此脫褲子放屁多此一舉,直接把hi函式賦值給greeting變數即可:

const hi = name => `hi, $`

const greeting = hi

懂了嗎?那再看下下面這個:

// 太傻了

const getserverstuff = callback => ajaxcall(json => callback(json)) // ajaxcall 是外部封裝好的介面函式

這麼長的函式,看都看不懂,我們來簡化下:

// 這行

ajaxcall(json => callback(json));

// 等價於這行

ajaxcall(callback);

// 那麼,重構下 getserverstuff

const getserverstuff = callback => ajaxcall(callback);

// ...就等於

const getserverstuff = ajaxcall //

拆到底就是個賦值操作而已~ 一定要避免這種氣人又尷尬的函式啊~

純函式是這樣一種函式,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的***。

從這句話中可以看出要成為純函式有兩個充分必要條件:

相同的輸入會得到相同的輸出(也叫引用透明性)。比如若是函式的輸入引數是數字返回值是陣列,就不會發生輸入引數是數字返回值是數字的情況。

沒有任何可觀察的***。也就是說函式在執行的過程中不依賴於外部的狀態 / 也不會改變外部的狀態。

比如對於slicesplice函式,輸入數字引數,總能返回陣列。但是不同之處在於splice在執行過程中會改變原陣列,而slice在沒有這樣的***。所以slice是純函式而splice不是,如下所示:

let nums = [1, 2, 3, 4, 5]

let a = nums.slice(0,2) // [1, 2]

console.log(nums) // [1, 2, 3, 4, 5]

let nums = [1, 2, 3, 4, 5]

let b = nums.splice(0, 2) // [1, 2]

console.log(nums) // [3, 4, 5]

戲劇性的是:純函式就是數學上的函式,而且是函式式程式設計的全部。

如何才能編寫無***的純函式是學習函式式程式設計的關鍵。

因為不依賴外部的狀態,所以純函式程式設計需要借助一些工具函式,來使函式的傳參看起來優雅且容易理解。

函式式程式設計屬於宣告式程式設計正規化,函式式程式設計的目的是使用函式來抽象作用在資料之上的控制流和操作,從而在系統中消除***並減少對狀態的改變。

// 命令式方式

var array = [0, 1, 2, 3]

for(let i = 0; i     array[i] = math.pow(array[i], 2)

}array // [0, 1, 4, 9]

// 宣告式方式

[0, 1, 2, 3].map(num => math.pow(num, 2)) // [0, 1, 4, 9]

命令式程式設計注重**執行過程,上面**使用了命令控制結構for迴圈,正是這種命令控制語句導致了**的死板和難以復用。函式式程式設計不關注**具體如何執行和資料如何穿過函式,而關注**執行結果。這也決定了函式式程式設計需要倚重一些工具函式( 如map filter ruduce find等陣列函式和curry compose等工具函式 )。

俗話說一口吃不成胖子,記得第一次接觸柯里化是在紅寶書裡面,當時看了好幾遍還是一臉懵逼,不知所云。所以柯里化函式是需要一定能力的抽象思維的。

柯里化是一種將使用多個引數的函式轉換成一系列使用乙個或多個引數的函式的技術。你可以一次性地傳遞所有引數呼叫函式,也可以每次只傳乙個引數分多次呼叫。

curry 函式的原理是利用閉包將原函式的一部分引數存起來,如果引數小於原函式形參的數量就返回乙個新函式以便繼續傳參呼叫,如果引數等於原函式的引數了就執行原函式。

import  from 'lodash'

function add(a, b) 

var addcurry = curry(add) // 柯里化add

var increment = addcurry(1) // 生成新函式 increment 執行會將入參增加 1

increment(10) // 11

var addten = addcurry(10)) // 生成新函式 increment 執行會將入參增加 1

addten(1) // 生成新函式 addten 執行會將入參增加 10

可以看到利用柯里化技術,能夠生成固定了部分引數的新函式。

我們認為組合是高於其他所有原則的設計原則,這是因為組合讓我們的**簡單而富有可讀性。

組合顧名思義就是將不同的函式組合起來生成乙個新的函式。組合的引數必須都是純函式。

function compose (...fns) 

return fn(arg)

}, args)}}

function touppercase(str) 

function split(str)

function reverse(arr)

function join(arr)

const turnstr = compose(join, reverse, split, touppercase)

turnstr('emosewa si nijeuj') // juejin is awesome

組合函式好像乙個管道一樣,將引數從右向左依次執行並將結果傳遞給左邊的引數。

組合函式還符合結合律,組合的呼叫分組不重要,所有結果都是一樣的:

const turnstr1 = compose(compose(join, reverse), split, touppercase)

turnstr1('emosewa si nijeuj') // juejin is awesome

const turnstr2 = compose(compose(join, reverse), split, touppercase)

turnstr2('emosewa si nijeuj') // juejin is awesome

結合律的一大好處是任何乙個函式分組都可以被拆開來,然後再以它們自己的組合方式打包在一起。當然這個就看個人的抽象能力和編碼能力了。

函式式程式設計不是一朝一夕就能學會的,而是要在實際開發中逐漸學習和熟練的。在實際程式設計中,盡量的利用curry compose等工具函式和遞迴將**控制邏輯抽先化,逐漸捨棄命令式開發而習慣編寫宣告式的**。

JavaScript函式式程式設計之副作用

概念 是在計算結果的過程中,系統狀態的一種變化,或者與外部世界進行的可觀察的互動。上文中的純函式的概念很嚴格,這個 的概念也是。它的要求很高,概括的講,只要是跟函式外部環境發生的互動就都是 從 這個詞語來看,它更多的情況在於 改變系統狀態 在教程中列舉的一些 如果完全沒有 那我們的 就是單純的跑一遍...

則該函式或表示式有副作用

在第三行中,x和y是遞增 遞減之前,他們的評價,所以他們的新值列印由cout。在第五行,乙個原始值的臨時副本 x 6,y 4 傳送給cout,然後原來的x和y是遞增的。這就是為什麼從字尾式操作符的結果沒有改變到下一行。規則 在增量和後減量後有利於預增加和預減量。字首版本不僅更加高效,你就不太可能遇到...

面向過程程式設計,函式式程式設計

面向過程程式設計,函式式程式設計 峰哥原創面向過程解釋 函式的引數傳入,是函式吃進去的食物,而函式return的返回值,是函式拉出來的結果,面向過程的思路就是,把程式的執行當做一串首尾相連的函式,乙個函式吃,拉出的東西給另外乙個函式吃,另外乙個函式吃了再繼續拉給下乙個函式吃。面向過程 機械式思維,流...