亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

從JavaScript純函數(shù)解析最深刻的函子 Monad實例

 更新時間:2022年10月15日 09:49:45   作者:掘金安東尼  
這篇文章主要為大家介紹了從JavaScript純函數(shù)解析最深刻的函子 Monad實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

序言

轉(zhuǎn)眼間,來到專欄第 3 篇,前兩篇分別是:

?從歷史講起,JavaScript 基因里寫著函數(shù)式編程

?從柯里化講起,一網(wǎng)打盡 JavaScript 重要的高階函數(shù)

建議按順序“食用”。飲水知其源,由 lambda 演算演化而來的閉包思想是 JavaScript 寫在基因里的東西,閉包的“孿生子”柯里化,是封裝高階函數(shù)的利器。

當(dāng)我們頻繁使用高階函數(shù)、甚至自己不斷在封裝高階函數(shù)的時候,其實就已經(jīng)把“函數(shù)是一等公民”這個最核心的函數(shù)式編程思想根植在心里面了。

函數(shù)可以作為參數(shù)、可以作為返回值、可以賦值給變量......

本篇帶來 JavaScript 函數(shù)式編程思想中最重要的概念之一 —— 純函數(shù),它定義了:寫出怎樣的函數(shù)才是優(yōu)雅的! 由純函數(shù)概念衍生,我們將進(jìn)一步探討:

  • 函數(shù)的輸入和輸出
  • 函數(shù)的副作用
  • 組合函數(shù)
  • 無形參風(fēng)格編程
  • 以及最后將一窺較難理解的函子 Monad 概念

話不多說,趕緊沖了~

純函數(shù)

什么樣的函數(shù)才算“純”?

緊扣定義,滿足以下兩個條件的函數(shù)可以稱作純函數(shù):

  • 如果函數(shù)的調(diào)用參數(shù)相同,則永遠(yuǎn)返回相同的結(jié)果。它不依賴于程序執(zhí)行期間函數(shù)外部任何狀態(tài)或數(shù)據(jù)的變化,必須只依賴于其輸入?yún)?shù)。
  • 該函數(shù)不會產(chǎn)生任何可觀察的副作用,例如網(wǎng)絡(luò)請求,輸入和輸出設(shè)備或數(shù)據(jù)突變(mutation)

輸入 & 輸出

在純函數(shù)中,約定:相同的輸入總能得到相同的輸出。而在日常 JavaScript 編程中,我們并沒有刻意保持這一點,這會導(dǎo)致很多“意外”。

?? 比如:分不清 slice 和 splice 的區(qū)別

var arr = [1,2,3,4,5];
arr.slice(0,3); // [1,2,3]
arr.slice(0,3); // [1,2,3]
arr.slice(0,3); // [1,2,3]
var arr = [1,2,3,4,5];
arr.splice(0,3); // [1,2,3]
arr.splice(0,3); // [4,5]
arr.splice(0,3); // []

使用 slice 無論多少次,相同的輸入?yún)?shù),都會有相同的結(jié)果;而 splice 則不會,splice 會修改原數(shù)組,導(dǎo)致即使參數(shù)完全相同,結(jié)果竟然完全不同。

在數(shù)組中,類似的、會對原數(shù)組修改的方法還有不少:pop()、push()、shift()、unshift()、reverse()、sort()、splice() 等,閱讀代碼時,想要得到原數(shù)組最終的值,必須追蹤到每一次修改,這會大幅降低代碼的可讀性。

?? 比如: random 函數(shù)的不確定

Math.random() // 0.9706010566439833
Math.random() // 0.26820889412263416
Math.random() // 0.6144693062318409

Math.random() 每次運行,都會產(chǎn)生一個介于 0 和 1 之間的新隨機(jī)數(shù),你無法預(yù)測它,相同的輸入、不通的輸出,意外 + 1;

相似的還有 new Date() 函數(shù),每次相同的調(diào)用,結(jié)果不一致;

new Date().toLocaleTimeString() // '11:43:44'
new Date().toLocaleTimeString() // '11:44:16'

?? 比如:有隱式輸出的函數(shù)

var tax = 20;
function calculateTax(productPrice) {
    tax = tax/100
    return (productPrice * tax) + productPrice;
}
calculateTax(100) // 120
calculateTax(100) // 100.2

上面 calculateTax 函數(shù)是一個比較隱蔽的非純函數(shù),輸入相同的參數(shù),得到不同的結(jié)果。

究其原因是因為函數(shù)輸出依賴外部變量 tax,并在無意中修改了外部變量。

所以,綜上,純函數(shù)必須要是:有相同的輸入就必須有相同輸出的這樣的函數(shù),運行一次是這樣,運行一萬次也應(yīng)該是這樣。

副作用

除了保障相同的輸入得到相同的輸出這一點外,純函數(shù)還要求:不會產(chǎn)生任何可觀察的副作用。

副作用指當(dāng)調(diào)用函數(shù)時,除了返回可能的函數(shù)值之外,還對主調(diào)用函數(shù)產(chǎn)生附加的影響。

副作用主要包含:

  • 可變數(shù)據(jù)
  • 打印/log
  • 獲取用戶輸入
  • DOM 查詢
  • 發(fā)送一個 http 請求
  • Math.random()
  • 獲取的當(dāng)前時間
  • 訪問系統(tǒng)狀態(tài)
  • 更改文件系統(tǒng)
  • 往數(shù)據(jù)庫插入記錄

?? 舉一些常見的有副作用的函數(shù)例子:

// 修改函數(shù)外部數(shù)據(jù)

let num = 0
function sum(x,y){
    num = x + y
    return num
}

// 調(diào)用 I/O

function sum(x,y){
    console.log(x,y)
    return x+y
}

// 引用函數(shù)外檢索值

function of(){
    return this._value
}

// 調(diào)用磁盤方法

function getRadom(){
    return Math.random()
}

// 拋出異常

function sum(x,y){
    throw new Error()
    return x + y
}

我們不喜歡副作用,它充滿了不確定性,我們的函數(shù)不是一個穩(wěn)定的黑盒,假設(shè) function handleA() 函數(shù),我們只期望它的功能是 A 操作,不希望它意外的又操作了 B 或 C。

所以,我們在純函數(shù)內(nèi)幾乎不去引用、修改函數(shù)外部的任何變量,僅僅通過最初的形參輸入,經(jīng)過一系列計算后再 return 返回給外部。

但副作用真的太常見了,有時候難以避免使用帶副作用的非純函數(shù)。在 JavaScript 函數(shù)式編程中,我們并不是倡導(dǎo)嚴(yán)格控制函數(shù)不帶一點副作用,而是要盡量把這個“危險的玩意”控制在可控的范圍內(nèi)。后面會講到如何控制非純函數(shù)的副作用。

“純”的好處

說了這么多關(guān)于“純函數(shù)”概念,肯定有人會問:寫純函數(shù)有什么好處?我為什么要寫純函數(shù)?

自文檔化

函數(shù)越純,它的功能越明確,不需要你閱讀它的時候還翻前找后,代碼本身就是文檔,甚至讀一下方法名就能放心的使用它,而不用擔(dān)心它還會不會有其它的影響。這就是代碼的自文檔化。

??舉個例子:

實現(xiàn)一個登錄功能:

// 非純函數(shù)

var signUp = function(attrs) {
  var user = saveUser(attrs);
  welcomeUser(user);
};
var saveUser = function(attrs) {
    var user = Db.save(attrs);
    ...
};
var welcomeUser = function(user) {
    Email(user, ...);
    ...
};

// 純函數(shù)

var signUp = function(Db, Email, attrs) {
  return function() {
    let user = saveUser(Db, attrs);
    welcomeUser(Email, user);
  };
};
var saveUser = function(Db, attrs) {
    ...
};
var welcomeUser = function(Email, user) {
    ...
};

在純函數(shù)表達(dá)中,每個函數(shù)需要用到的參數(shù)更明確、調(diào)用關(guān)系更明確,為我們提供了更多的基礎(chǔ)信息,代碼信息自成文檔。

組合函數(shù)

本瓜常提的“組合函數(shù)”就是純函數(shù)衍生出來的一種函數(shù)。把一個純函數(shù)的結(jié)果作為另一個純函數(shù)的輸入,最終得到一個新的函數(shù),就是組合函數(shù)。

const componse = (...fns) => fns.reduceRight((pFn, cFn) => (...args) => cFn(pFn(...args)))
function hello(name) { return `HELLO ${name}` }
function connect(firstName, lastName) {   return firstName + lastName; } 
function toUpperCase(name) {   return name.toUpperCase() }
const sayHello = componse(hello, toUpperCase, connect)
console.log(sayHello('juejin', 'anthony')) // HELLO JUEJINANTHONY

多個純函數(shù)組合起來的函數(shù)也一定是純函數(shù)。

引用透明性

引用透明性是指一個函數(shù)調(diào)用可以被它的輸出值所代替,并且整個程序的行為不會改變。

我們可以利用這個特性對純函數(shù)進(jìn)行“加和乘”的運算,這是重構(gòu)代碼的絕妙手段之一~

??比如:

優(yōu)化以下代碼:

var Immutable = require('immutable');
var decrementHP = function(player) {
  return player.set("hp", player.hp-1);
};
var isSameTeam = function(player1, player2) {
  return player1.team === player2.team;
};
var punch = function(player, target) {
  if(isSameTeam(player, target)) {
    return target;
  } else {
    return decrementHP(target);
  }
};
var jobe = Immutable.Map({name:"Jobe", hp:20, team: "red"});
var michael = Immutable.Map({name:"Michael", hp:20, team: "green"});
punch(jobe, michael);

因為 decrementHPisSameTeam 都是純函數(shù),我們可以用等式推導(dǎo)、手動執(zhí)行、值的替換來簡化代碼:

因為數(shù)據(jù)不可變,所以 isSameTeam(player, target) 替換成 "red" === "green",在 puch 函數(shù)內(nèi),if(false){...} 則直接刪掉,然后將 decrementHP 函數(shù)內(nèi)聯(lián),最終簡化為:

var punch = function(player, target) {
  return target.set("hp", target.hp-1);
};
var jobe = Immutable.Map({name:"Jobe", hp:20, team: "red"});
var michael = Immutable.Map({name:"Michael", hp:20, team: "green"});
punch(jobe, michael);

純函數(shù)的引用透明性讓純函數(shù)能做簡單運算及替換,在重構(gòu)中能大大減少代碼量。

其它

  • 純函數(shù)不需要訪問共享的內(nèi)存,這也是它的決定性好處之一。這樣一來,它無需處于競爭態(tài),使得 JS 在服務(wù)端的并行能力極大提高。
  • 純函數(shù)還能讓測試更加容易。我們不需要模擬一個真實的場景,只需要簡單模擬函數(shù)的輸入、然后斷言輸出即可。
  • 純函數(shù)與運行環(huán)境無關(guān),只要愿意嗎,可以在任何地方移植它、運行它,其本身已經(jīng)撇除了函數(shù)所攜帶的的各種隱式環(huán)境,這是命令式編程的弊病之一。

言而總之,函數(shù)盡量寫“純”一點,好處真的有很多~ 寫著寫著就知道了

無形參風(fēng)格

純函數(shù)的引用透明性可以等式推導(dǎo)演算,在函數(shù)式編程中,有一種流行的代碼風(fēng)格和它很相似,如出一轍。

這種風(fēng)格就是無形參風(fēng)格,其目的是通過移除不必要的形參-實參映射來減少視覺上的干擾。

??舉例說明:

function double(x) {
    return x * 2;
}
[1,2,3,4,5].map( function mapper(v){
    return double( v );
} );

double 函數(shù)和 mapper 函數(shù)有著相同的形參,mapper 的參數(shù) v 可以直接映射到 double 函數(shù)里的實參里,所以 mapper(..) 函數(shù)包裝是非必需的。我們可以將其簡化為無形參風(fēng)格:

function double(x) {
    return x * 2;
}
[1,2,3,4,5].map( double );
// [2,4,6,8,10]

無形參可以提高代碼的可讀性和可理解性。

其實我們也能看出只有純函數(shù)的組合才能更利于寫出無形參風(fēng)格的代碼,看起來更優(yōu)雅~

Monad

前面一直強(qiáng)調(diào):純函數(shù)!無副作用!

談何容易?HTTP 請求、修改函數(shù)外的數(shù)據(jù)、輸出數(shù)據(jù)到屏幕或控制臺、DOM查詢/操作、Math.random()、獲取當(dāng)前時間等等這些操作都是我們經(jīng)常需要做的,根本不可能擯棄它們,不然連最基礎(chǔ)功能都實現(xiàn)不了。。。

解決上述矛盾,這里要拋出一個哲學(xué)問題:

你是否能知道一間黑色的房間里面有沒有一只黑色的貓?

明顯是不能的,直到開燈那一刻之前,把一只貓藏在一間黑色的屋子里,和一間干凈的黑屋子都是等效的。

所以,對了!我們可以把不純的函數(shù)用一間間黑色屋子裝起來,最后一刻再亮燈,這樣能保證在亮燈前一刻,一直都是“純”的。

這些屋子就是單子 —— “Monad”!

??舉個例子,用 JavaScript 模擬這個過程:

var fs = require("fs");
// 純函數(shù),傳入 filename,返回 Monad 對象
var readFile = function (filename) {
  // 副作用函數(shù):讀取文件
  const readFileFn = () => {
    return fs.readFileSync(filename, "utf-8");
  };
  return new Monad(readFileFn);
};
// 純函數(shù),傳入 x,返回 Monad 對象
var print = function (x) {
  // 副作用函數(shù):打印日志
  const logFn = () => {
    console.log(x);
    return x;
  };
  return new Monad(logFn);
};
// 純函數(shù),傳入 x,返回 Monad 對象
var tail = function (x) {
  // 副作用函數(shù):返回最后一行的數(shù)據(jù)
  const tailFn = () => {
    return x[x.length - 1];
  };
  return new Monad(tailFn);
};
// 鏈?zhǔn)讲僮魑募?
const monad = readFile("./xxx.txt").bind(tail).bind(print);
// 執(zhí)行到這里,整個操作都是純的,因為副作用函數(shù)一直被包裹在 Monad 里,并沒有執(zhí)行
monad.value(); // 執(zhí)行副作用函數(shù)

readFile、print、tail 函數(shù)最開始并非是純函數(shù),都有副作用操作,比如讀文件、打印日志、修改數(shù)據(jù),然而經(jīng)過用 Monad 封裝之后,它們可以等效為一個個純函數(shù),然后通過鏈?zhǔn)浇壎?,最后調(diào)用執(zhí)行,也就是開燈。

在執(zhí)行 monad.value() 這句之前,整段函數(shù)都是“純”的,都沒有對外部環(huán)境做任何影響,也就意味著我們最大程度的保證了“純”這一特性。

王垠在《對函數(shù)式語言的誤解》中準(zhǔn)確了描述了 Monad 本質(zhì):

Monad 本質(zhì)是使用類型系統(tǒng)的“重載”(overloading),把這些多出來的參數(shù)和返回值,掩蓋在類型里面。這就像把亂七八糟的電線塞進(jìn)了接線盒似的,雖然表面上看起來清爽了一些,底下的復(fù)雜性卻是不可能消除的。

上述的 Monad 只是最通俗的理解,實際上 Monad 還有很多分類,比如:Maybe 單子、List 單子、IO 單子、Writer 單子等,后面再討論~

結(jié)語

本篇從純函數(shù)出發(fā),JavaScript 函數(shù)要寫的優(yōu)雅,一定要“純”!寫純函數(shù)、組合純函數(shù)、簡化運算純函數(shù)、無形參風(fēng)格、純函數(shù)的鏈?zhǔn)秸{(diào)用、Monad 封裝不存的函數(shù)讓它看起來“純”~

更多關(guān)于JavaScript純函數(shù)Monad的資料請關(guān)注腳本之家其它相關(guān)文章!

  • 深入理解JavaScript系列(14) 作用域鏈介紹(Scope Chain)

    深入理解JavaScript系列(14) 作用域鏈介紹(Scope Chain)

    在第12章關(guān)于變量對象的描述中,我們已經(jīng)知道一個執(zhí)行上下文 的數(shù)據(jù)(變量、函數(shù)聲明和函數(shù)的形參)作為屬性存儲在變量對象中
    2012-04-04
  • JavaScript 如何刪除小數(shù)點后的數(shù)字

    JavaScript 如何刪除小數(shù)點后的數(shù)字

    這篇文章主要介紹了JavaScript 刪除小數(shù)點后的數(shù)字實例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • weui框架實現(xiàn)上傳、預(yù)覽和刪除圖片功能代碼

    weui框架實現(xiàn)上傳、預(yù)覽和刪除圖片功能代碼

    weui框架暫時只有css文件,并沒有js文件實現(xiàn)其功能,我在其html+css后面增加了js實現(xiàn)其功能,為大家提供方便,也為自己保存記錄。具體實例代碼大家參考下本文
    2017-08-08
  • javascript 內(nèi)存回收機(jī)制理解

    javascript 內(nèi)存回收機(jī)制理解

    javascript語言是一門優(yōu)秀的腳本語言.其中包含腳本語言的靈活性外還擁有許多高級語言的特性.
    2011-01-01
  • 聊聊鑒權(quán)那些事(推薦)

    聊聊鑒權(quán)那些事(推薦)

    這篇文章主要介紹了聊聊鑒權(quán)那些事(推薦),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 用js寫的一個路由(簡單實例)

    用js寫的一個路由(簡單實例)

    下面小編就為大家?guī)硪黄胘s寫的一個路由(簡單實例)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • javascript語句中的CDATA標(biāo)簽的意義

    javascript語句中的CDATA標(biāo)簽的意義

    javascript語句中的CDATA標(biāo)簽的意義...
    2007-05-05
  • 淺談JavaScript中指針和地址

    淺談JavaScript中指針和地址

    Javascript是一門基于對象的動態(tài)語言,也就是說,所有東西都是對象,一個很典型的例子就是函數(shù)也被視為普通的對象。Javascript可以通過一定的設(shè)計模式來實現(xiàn)面向?qū)ο蟮木幊蹋渲衪his指針就是實現(xiàn)面向?qū)ο蟮囊粋€很重要的特性。
    2015-07-07
  • 小程序自定義tabbar導(dǎo)航欄及動態(tài)控制tabbar功能實現(xiàn)方法(uniapp)

    小程序自定義tabbar導(dǎo)航欄及動態(tài)控制tabbar功能實現(xiàn)方法(uniapp)

    在項目中遇到一個需求,根據(jù)不同的賬號,生成不同的tabBar,下面這篇文章主要給大家介紹了關(guān)于小程序自定義tabbar導(dǎo)航欄及動態(tài)控制tabbar功能實現(xiàn)方法(uniapp)的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • 最新評論