函數式編程中的副作用概念

2020-12-25 前端學習棧

前言

為了清楚起見,請記住,副作用不是必需的壞事,有時副作用是有用的(尤其是在函數式編程範式之外)。

今天聊一聊函數式編程中的隔離思想,它所想隔離的就是「副作用」

我們先從其他角度來聊一聊副作用這個概念。

生活中的副作用

如果我聽到副作用這個詞後,第一反應是吃藥 。

老話說是藥三分毒,其中三分毒則為副作用。就比如你感冒了,吃了一些西方某些國家研製的專利藥品,然後感冒好了,但是感冒好了之後發現自己禿頂 了。

那麼可以說禿頂就是這個感冒藥的副作用。

我們來捋一下這個邏輯

感冒好沒好? 答:好了這藥算不算感冒藥 ? 答:算感冒藥不吃這個藥的話感冒就不會好,吃不吃 ? 答:吃副作用可不可以忍受 ? 答:至少本來就沒頭髮可以忍上面的副作用有些誇大其詞了,但是藥物一般來說都會有一些副作用。

那麼話說回來,程序中呢?

程序的副作用是什麼

在I/O模型中,我們希望在在I到O之間只有計算,如果中間包含且不僅包含觸發了其他I/O、與此次I -> O計算並不相關的任何事情,都稱為副作用。

為什麼稱之為副作用這樣的詞語呢,「副作用」這個單詞給人第一感覺是糟糕的,從而想讓你警惕起來。如果在I/O之間發生了一些我們不知道的副作用,那麼我們將無法控制住這個過程,測試過程也會變得非常複雜。

可以想像,如果在I/O之間如果要訪問資料庫,則必須確保資料庫正在運行。如果要寫入文件,則必須確保該文件存在並已打開。所以會導致執行過程和測試過程變得很複雜,並不是單一的點對點。

寫到這裡讓我不禁想起了PromiseA+規範的測試用例,官方提供了872種測試用例,你所實現的Promise必須全部通過872種測試用例才符合官方規範。

無副作用的優勢

如果一個I/O模型之間沒有副作用的話會有什麼樣的優勢呢?我們參照最開始生活角度的那個例子。

如果感冒藥換成某東方國家生產的無副作用的藥品,在我們感冒的時候,吃感冒藥,感冒好了。過程中無任何副作用的產生,不會禿頂。

那麼我們就可以放心的在感冒的情況下吃這種藥品,而不用考慮其他情況。這就是一個純的I/O模型。

在編程中,我們聲明了一個函數double,如下

在每次輸入x = 3的情況下,double返回恆等與6。它不依賴於我們傳遞它的參數之外的任何東西。

可以想像,在我們調用double的時候,地球上發生著各種各樣的事情,如果在調用的瞬間,天上出現了奧特曼,double依然輸出6。在固定輸入的情況下它是永恆的。

當你寫下這個函數之後,你的餘生都可以放心使用它,無論上下文如何,它將永久有效。

永恆的東西變化的頻率較低,測試起來更加容易,調試起來更加容易。這就是為什麼現在很多程式語言都傾向於無副作用。

函數式編程中的副作用概念

如果函數有副作用,我們將其稱為過程

函數式編程是基於沒有副作用的這樣一個簡單的前提。在這種範例中,副作用是被排斥的。

如果函數有副作用,我們將其稱為過程,或者命令式。因此函數沒有副作用。我們認為,如果函數修改了可變數據結構或變量,使用I/O,引發異常或中止錯誤,則將產生副作用。所有這些東西都被認為是副作用。

副作用之所以不好,是因為(如果有)副作用,取決於系統狀態,功能可能是不可預測的。當一個函數沒有副作用時,我們可以隨時執行它,在給定相同的輸入的情況下,它將始終返回相同的結果。

但是要聲明一點,函數式編程並不是不需要副作用,只是在需要時限制它們。

需要有副作用,因為沒有它們,我們的程序將只能進行計算。

我們經常必須寫資料庫,與外部系統集成或寫文件。與外界通過接口的形式交互才能將我們的計算展示出去。所以很多傾向無副作用的語言的中心細想是把「作用」與「副作用」分離開來處理。

下面我們通過一些特性來看一下。

參照透明

對於同一輸入總是返回相同結果的函數稱為純函數。因此,純函數是沒有可觀察到的副作用的函數,如果函數有任何副作用,即使我們使用相同的參數調用它,也可能返回不同的結果。所以我們可以將純函數替換為其計算值,例如:

如果我們輸入x = 2, y = 2,那麼我們可以得到 4 = sum(2, 2)。

那麼sum為純函數嗎?很顯然是的,如果我們恆定傳入x = 2, y = 2。那麼sum將恆定輸出4.

那麼意味著 f(2, 2) 可以替換為4,比如 Math.floor(sum(2, 2)) 替換之後 Math.floor(4),是一致的。

它就像一個很大的查詢表。我們可以這樣做是因為它沒有任何副作用。用其計算值替換表達式的能力稱為參照透明性。

引用透明很重要,因為它允許我們用值替換表達式。此屬性使我們能夠使用替換模型來思考和推理程序評估。因此,可以說可以用值替換的表達式是確定性的,因為它們始終為給定的輸入返回相同的值。

局部副作用

在講局部副作用之前,我們先來舉一個非局部副作用的典型例子。

上述的factorial函數有副作用嗎?

答:很顯然是有的。函數內部與外界產生了可見的交互,外界result值在函數內部被修改了。而且第一次調用factorial(2) 返回值為 3,第二次調用返回值為6。對於統一輸入不能總返回同一結果。這種副作用是被函數式編程思想所排斥的,與外界的交互使得factorial具有不確定性。

接下來我們看一下另一個例子

那麼問題來了,這次factorial有副作用嗎?

答:有副作用,因為for每次執行的時候都會改變factorial的返回值,result在不斷改變。

但是即使這樣,factorial(2) 也可以用一個值代替,如果把factorial看作一個黑盒子,從外部我們是看不到副作用的。每次的輸入x = 2,總會有固定的返回值3。

換句話說,該函數是具有確定性的,我們說的功能有局部副作用,但此功能的用戶並不關心,因為它沒有破壞我們的替代模式。因此,即使具有局部副作用,該函數也是純淨的。這也是上面為什麼說「產生了可見的交互」,很顯然這句話就是這麼嚴謹,如果見不到,依然是純的。

在函數式編程開發中,可以用一些技巧,比如利用容器,把一些副作用控制在局部以達到純的目的。

舉一些副作用的典型例子

想了想還是在這裡立舉一些典型有副作用的例子,通過例子可以更好的理解這種思想。

1、與外界交互的。

2、調用I/O的

3、從函數範圍之外檢索值

4、磁碟檢索

5、拋出異常

相關焦點

  • Swift 不是多範式函數式程式語言
    一個無法在O(1)時間內簡單地將一個列表折分出「第一個」和「不是第一個」元素的「函數式」語言是一種非常奇怪的函數式語言。Swift特有reduce和map函數,它還具有第一類函數和模式匹配。並且,它還具有在其它函數式語言中也很常見的一些特性。
  • 如何寫好JavaScript函數?
    讀者可能無法判斷某個表達式是基礎概念還是細節。更惡劣的是,就像破損的窗戶,一旦細節和基礎概念混雜,更多的細節就會在函數中糾結起來。參數越多,函數越不容易理解,同時編寫能確保參數的各種組合運行正常的測試用例越困難。5、避免副作用如果一個函數不是獲取一個輸入的值並返回其它值,它就有可能產生副作用。這些副作用可能是寫入文件、修改一些全局變量、屏幕列印或者日誌記錄、查詢HTML文檔、瀏覽器的cookie或訪問資料庫。
  • 讓你徹底明白yield語法糖的用法和原理及在C 函數式編程中的作用
    通過使用 yield 定義迭代器,可在實現自定義集合類型的 IEnumerator 和 IEnumerable 模式時無需其他顯式類(保留枚舉狀態的類,有關示例,請參閱 IEnumerator)。沒用過yield之前,看這句話肯定是一頭霧水,只有在業務開發中踩過坑,才能體會到yield所帶來的快感。
  • 高中數學《函數的概念》說課稿
    一、說教材首先談談我對教材的理解,《函數的概念》是北師大版必修一第二章2.1的內容,本節課的內容是函數概念。函數內容是高中數學學習的一條主線,它貫穿整個高中數學學習中。又是溝通代數、方程、、不等式、數列、三角函數、解析幾何、導數等內容的橋梁,同時也是今後進一步學習高等數學的基礎。
  • C語言編程技巧:如何在函數中正確返回字符串的指針
    問題提出在C語言編程中,我們經常會遇到這種情況,在某個函數中經過算法處理以後得到一個字符串類型的結果,可能需要將這個字符串以指針的形式進行返回,那麼如何在函數中正確返回該字符串的內容呢?例如,定義一個函數,要求該函數能夠返回一個指向字符串「I love C.」的指針並能在主程序中正確得到該字符串的內容。
  • JAVA並發編程:並發問題的根源及主要解決方法
    這就會出現這樣一種情況,線程1修改了變量A,但此時修改後的變量A只存儲在CPU緩存中。這時候線程B去內存中讀取變量A,依舊只讀取到舊的值,這就是可見性問題。線程切換帶來的原子性為了更充分得利用CPU,引入了CPU時間片時間片的概念。進程或線程通過爭用CPU時間片,讓CPU可以更加充分地利用。
  • 編程大牛的哪個習慣,值得你學習!
    【IT168 評論】作為一名開發者,無論我們剛處於起步階段還是已經工作多年,我們總是應該努力提高我們的編程技能。我們需要不斷嘗試新的工具,語言和技術,不斷充實我們的技能包。除了我們知道的這些技術、程式語言之外,我們還可以做什麼呢?
  • 數字式正餘弦函數產生器的實現方法
    正餘弦函數在許多的電子設備的波形產生器中都用到,實現的方法也有很多種。在雷達顯示器中由於要求的解析度較高,最常用的方法是採用「查表」的方法來得到函數值。這種方法用函數表存貯器代替大量的組合電路,在電路集成度不高的情況下不失為一種好的方法。隨著大規模集成電路技術的發展,尤其是近幾年來現場可編程門陣列(FPGA)規模及處理速度的提高,為我們設計數字式函數產生器提供了很好的環境。
  • 第16講 指數函數及其性質_基礎
    文章結尾的「閱讀全文」可以跳轉到知識點的講解部分和習題部分指數函數及其性質【學習目標】1.掌握指數函數的概念,了解對底數的限制條件的合理性,明確指數函數的定義域;3.學會利用指數函數單調性來比較大小,包括較為複雜的含字母討論的類型;4.通過對指數函數的概念、圖象、性質的學習,培養觀察、分析歸納的能力,進一步體會數形結合的思想方法;5.通過對指數函數的研究,要認識到數學的應用價值,更善於從現實生活中發現問題,解決問題.
  • 自創一門程式語言的14步
    自創一門程式語言的14步 現在,很多人對他們現在每天使用的程式語言感到困惑和不解,他們都有各自的需求,無論你是一名職業的 IT 人員還是普通的開發愛好者,你可能想要創造一門新的程式語言。下面就來告訴你如何創造一門程式語言。
  • >重慶南坪協信星光小碼王少兒編程培訓
    少兒編程不僅僅只是一個課外學習課程,學習編程不僅可以提升孩子的思維能力,還可以將編程知識運用到語數外物理化學科中。小碼王少兒編程課程內容多種多樣,有興趣的家長可以根據孩子的基本情況選擇相對應的課程學習,現在小碼王還有線上免費課程,可以直接撥打客服電話諮詢領取。小碼王少兒編程補習班怎麼收費?
  • 利用EasyX圖像編程製作y=x^2函數圖像
    ;cmath>#include<conio.h>int main(){int x=-50;initgraph(100,200);SetWindowText(GetHWnd(),"二次函數圖像
  • HelloCode:機器人編程與軟體少兒編程的區別
    編程思維是人人都需要的,它教會我們的是更合乎邏輯的解決問題的方法,這種思維方式,即使不在編程教育中學習,在其他科目的學習中也應該竭力被培養。現在市場上有兩種教育方式:一種是機器人編程教育,另一種是少兒編程教育,那這兩種教育方式有什麼不同呢?
  • MXNet設計筆記之:深度學習的編程模式比較
    本文是第一篇設計筆記的譯文,深入討論了不同深度學習庫的接口對深度學習編程的性能和靈活性產生的影響。市面上流行著各式各樣的深度學習庫,它們風格各異。那麼這些函數庫的風格在系統優化和用戶體驗方面又有哪些優勢和缺陷呢?本文旨在於比較它們在編程模式方面的差異,討論這些模式的基本優劣勢,以及我們從中可以學到什麼經驗。我們主要關注編程模式本身,而不是其具體實現。
  • python高階函數:map、filter、reduce的替代品
    什麼是高階函數?高階函數是一種將函數作為參數,或者把函數作為結果返回的函數,map函數、sorted函數就是高階函數的典型例子。map函數在小編以前的文章中做過相應的知識分享。sorted函數是python的內置函數,它的可選參數key用於提供一個函數,它可以將函數應用到各個元素上進行排序。根據單詞長度,使用sorted函數對一個列表進行排序。
  • 看看9種程式語言的發明者是怎麼說的
    它要求用戶不斷的處理新事物,因此它很適用於網 絡編程。在你的伺服器上要與很多人打交道,你必須處理連結。Node鼓勵人們用非阻塞的模式。由於這個特性,你會發現Node在開發伺服器上比傳統編程語 言更加方便。
  • C語言函數調用過程中的內存變化解析
    相信很多編程新手村的同學們都會有一個疑問:C 語言如何調用函數的呢?局部變量的作用域為什麼僅限於函數內?這個調用不是指C 語言上的函數調用的語法,而是在內存的視角下,函數的調用過程。本文將從C 語言調用實例,內存視角,反彙編代碼來探討C 語言函數的調用過程,也可以說是C 語言函數調用過程圖解。通過這個C 語言函數調用過程圖解,同學們將會知道,C 語言函數在調用時,內存空間是怎樣變化的。 要想理解這一個過程還好涉及到函數棧幀的概念。
  • 信息學奧賽編程集訓0815班第18課學生編程作品
    主要應用到引用頭文件、命名空間、主函數、變量定義與應用、貪心算法、結構體應用、系統庫函數、for語句、if.else語句、數組定義與應用、函數定義、函數調用、標準輸入、標準輸出等編程技巧。 【編程技術】 編寫該程序應用到的編程技術有以下: 引用頭文件 命名空間 主函數 變量定義與應用 貪心算法
  • 第一篇:C語言編程基礎語法
    01編程基礎C語言——貝爾實驗室、Dennis Ritchie其特點:模塊化編程程序設計,層次清晰;語句簡潔,提出程序庫概念;功能強大,系統、應用軟體均可開發;移植性好,適合不同的作業系統。除此之外,C語言還有豐富的運算符、數據結構等,程序設計自由度大,可以對硬體進行操作。
  • 學Python編程為什麼會對學好數學有幫助呢?
    對外發布新的程式語言,需要給程式語言起個名稱,當時吉多迷上了英國肥皂劇《Monty Python飛行馬戲團》,他就從這個肥皂劇的名稱中選擇了Python。Monty Python是英國六人喜劇團體,他們製作的肥皂劇在七、八十年代特別受歡迎,吸引了眾多的粉絲,吉多就是其中的粉絲之一。因此,新的程式語言被吉多命名為Python,也就不足為怪了。