摘要:而且我們不需要了解的特別深,函數(shù)式編程很多概念是從范疇論映射過來的。了解范疇論相關(guān)概念有助于我們理解函數(shù)式編程。函子函子是用來將兩個(gè)范疇關(guān)聯(lián)起來的。
在前面幾篇介紹了函數(shù)式比較重要的一些概念和如何用函數(shù)組合去解決相對(duì)復(fù)雜的邏輯。是時(shí)候開始介紹如何控制副作用了。
數(shù)據(jù)類型我們來看看上一篇最后例子:
const split = curry((tag, xs) => xs.split(tag)) const reverse = xs => xs.reverse() const join = curry((tag, xs) => xs.join(tag)) const reverseWords = compose(join(""), reverse, split("")) reverseWords("Hello,world!");
這里其實(shí)reverseWords還是很難閱讀,你不知道他入?yún)⑹巧叮祷刂涤质巧?。你如果不去看一下代碼,一開始在使用他的時(shí)候,你應(yīng)該是比較害怕的。 “我是不是少傳了一個(gè)參數(shù)?是不是傳錯(cuò)了參數(shù)?返回值真的一直都是一個(gè)字符串嗎?”。這也是類型系統(tǒng)的重要性了,在不斷了解函數(shù)式后,你會(huì)發(fā)現(xiàn),函數(shù)式編程和類型是密切相關(guān)的。如果在這里reverseWords的類型明確給出,就相當(dāng)于文檔了。
但是,JavaScript是動(dòng)態(tài)類型語言,我們不會(huì)去明確的指定類型。不過我們可以通過注釋的方式加上類型:
// reverseWords: string => string const reverseWords = compose(join(""), reverse, split(""))
上面就相當(dāng)于指定了reverseWords是一個(gè)接收字符串,并返回字符串的函數(shù)。
JS 本身不支持靜態(tài)類型檢測(cè),但是社區(qū)有很多JS的超集是支持類型檢測(cè)的,比如Flow還有TypeScript。當(dāng)然類型檢測(cè)不光是上面所說的自文檔的好處,它還能在預(yù)編譯階段提前發(fā)現(xiàn)錯(cuò)誤,能約束行為等。
當(dāng)然我的后續(xù)文章還是以JS為語言,但是會(huì)在注釋里面加上類型。
范疇論相關(guān)概念范疇論其實(shí)并不是特別難,不過是些抽象點(diǎn)的概念。而且我們不需要了解的特別深,函數(shù)式編程很多概念是從范疇論映射過來的。了解范疇論相關(guān)概念有助于我們理解函數(shù)式編程。另外,相信我,只要你小學(xué)初中學(xué)過一元函數(shù)和集合,看懂下面的沒有問題。
定義范疇的定義:
一組對(duì)象,是需要操作的數(shù)據(jù)的一個(gè)集合
一組態(tài)射,是數(shù)據(jù)對(duì)象上的映射關(guān)系,比如 f: A -> B
態(tài)射組合,就是態(tài)射能夠幾個(gè)組合在一起形成一個(gè)新的態(tài)射
圖片出處:https://en.wikipedia.org/wiki...
一個(gè)簡(jiǎn)單的例子,上圖來自維基百科。上面就是一個(gè)范疇,一共有3個(gè)數(shù)據(jù)對(duì)象A,B,C,然后f和g是態(tài)射,而gof是一組態(tài)射組合。是不是很簡(jiǎn)單?
其中態(tài)射可以理解是函數(shù),而態(tài)射的組合,我們可以理解為函數(shù)的組合。而里面的一組對(duì)象,不就是一個(gè)具有一些相同屬性的數(shù)據(jù)集嘛。
函子(functor)函子是用來將兩個(gè)范疇關(guān)聯(lián)起來的。
圖片出處:https://ncatlab.org/nlab/show...
對(duì)應(yīng)上圖,比如對(duì)于范疇 C 和 D ,函子 F : C => D 能夠:將 C 中任意對(duì)象X 轉(zhuǎn)換為 D 中的 F(X); 將 C 中的態(tài)射 f : X => Y 轉(zhuǎn)換為 D 中的 F(f) : F(X) => F(Y)。你可以發(fā)現(xiàn)函子可以:
轉(zhuǎn)換對(duì)象
轉(zhuǎn)換態(tài)射
構(gòu)建一個(gè)函子(functor) Container正如上面所說,函子能夠關(guān)聯(lián)兩個(gè)范疇。而范疇里面必然是有一組數(shù)據(jù)對(duì)象的。這里引入Container,就是為了引入數(shù)據(jù)對(duì)象:
class Container { constructor (value) { this.$value = value } // (value) => Container(value) static of(value) { return new Container(value) } }
我們聲明了一個(gè)Container的類,然后給了一個(gè)靜態(tài)的of方法用于去生成這個(gè)Container的實(shí)例。這個(gè)of其實(shí)還有個(gè)好聽的名字,賣個(gè)關(guān)子,后面介紹。
我們來看一下使用這個(gè)Container的例子:
// Container(123) Container.of(123) // Container("Hello Conatiner!") Container.of("Hello Conatiner!") // Container(Conatiner("Test !")) Container.of(Container.of("Test !"))
正如上面看到的,Container是可以嵌套的。我們仔細(xì)看一下這個(gè)Contaienr:
$value的類型不確定,但是一旦賦值之后,類型就確定了
一個(gè)Conatiner只會(huì)有一個(gè)value
我們雖然能直接拿到$value,但是不要這樣做,不然我們要個(gè)container干啥呢
第一個(gè)functor讓我們回看一下定義,函子是用來將兩個(gè)范疇關(guān)聯(lián)起來的。所以我們還需要一個(gè)態(tài)射(函數(shù))去把兩個(gè)范疇關(guān)聯(lián)起來:
class Container { constructor (value) { this.$value = value } // (value) => Container(value) static of(value) { return new Container(value) } // (fn: x=>y) => Container(fn(value)) map(fn) { return new Container(fn(this.$value)) } }
先來用一把:
const concat = curry((str, xs) => xs.concat(str)) const prop = curry((prop, xs) => xs[prop]) // Container("TEST") Container.of("test").map(s => s.toUpperCase()) // Container(10) Container.of("bombs").map(concat(" away")).map(prop("length"));
不曉得上面的curry是啥的看第二篇文章。
你可能會(huì)說:“哦,這是你說的functor,那又有啥用呢?”。接下來,就講一個(gè)應(yīng)用。
不過再講應(yīng)用前先講一下這個(gè)of,其實(shí)上面這種functor,叫做pointed functor, ES5里面的Array就應(yīng)用了這種模式:Array.of。他是一種模式,不僅僅是用來省略構(gòu)建對(duì)象的new關(guān)鍵字的。我感覺和scala里面的compaion object有點(diǎn)類似。
Maybe type在現(xiàn)實(shí)的代碼中,存在很多數(shù)據(jù)是可選的,返回的數(shù)據(jù)可能是存在的也可能不存在:
type Person = { info?: { age?: string } }
上面是flow里面的類型聲明,其中?代表這個(gè)數(shù)據(jù)可能存在,可能不存在。我相信像上面的數(shù)據(jù)結(jié)構(gòu),你在接收后端返回的數(shù)據(jù)的時(shí)候經(jīng)常遇到。假如我們要取這個(gè)age屬性,我們通常是怎么處理的呢?
當(dāng)然是加判斷啦:
const person = { info: {} } const getAge = (person) => { return person && person.info && person.info.age } getAge(person) // undefined
你會(huì)發(fā)現(xiàn)為了取個(gè)age,我們需要加很多的判斷。當(dāng)數(shù)據(jù)中有很多是可選的數(shù)據(jù),你會(huì)發(fā)現(xiàn)你的代碼充滿了這種類型判斷。心累不?
Okey,Maybe type就是為了解決這個(gè)問題的,先讓我們實(shí)現(xiàn)一個(gè):
class Maybe { static of(x) { return new Maybe(x); } get isNothing() { return this.$value === null || this.$value === undefined; } constructor(x) { this.$value = x; } map(fn) { return this.isNothing ? this : Maybe.of(fn(this.$value)); } get() { if (this.isNothing) { throw new Error("Get Nothing") } else { return this.$value } } getOrElse(optionValue) { if (this.isNothing) { return optionValue } else { return this.$value } } }
應(yīng)用一波:
type Person = { info?: { age?: string } } const prop = curry((tag, xs) => xs[tag]) const map = curry((fn, f) => f.map(fn)) const person = { info: {} } // safe get age Maybe.of(person.info).map(prop("age")) // Nothing // safe get age Point free style const safeInfo = xs => Maybe.of(person.info) const getAge = compose(map(prop("age")), safeInfo) getAge(person) // Nothing
來復(fù)盤一波,上面的map依然是一個(gè)functor(函子)。不過呢,在做類型轉(zhuǎn)換的時(shí)候加上了邏輯:
map(fn) { return this.isNothing ? this : Maybe.of(fn(this.$value)); }
所以也就是上面的轉(zhuǎn)換關(guān)系可以表示為:
其實(shí)一看圖就出來了,“哦,你把判斷移動(dòng)到了map里面。有啥用?”。ok,羅列一下好處:
更安全
將判斷邏輯進(jìn)行封裝,代碼更簡(jiǎn)潔
聲明式代碼,沒有各種各樣的判斷
其實(shí),不確定性,也是一種副作用。對(duì)于可選的數(shù)據(jù),我們?cè)谶\(yùn)行時(shí)是很難確定他的真實(shí)的數(shù)據(jù)類型的,我們用Maybe封裝一下其實(shí)本身就是封裝這種不確定性。這樣就能保證我們的一個(gè)入?yún)⒅挥锌赡軙?huì)返回一種輸出了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98777.html
摘要:函子上面容器上定義了方法,的定義也類似是實(shí)現(xiàn)了函數(shù)并遵守一些特定規(guī)則的容器類型。不同類型的函子容器在處理內(nèi)部值時(shí),經(jīng)常遇到傳入?yún)?shù)異常的情況的情況,檢查值的合理性就非常重要。函子保證在調(diào)用傳入的函數(shù)之前,檢查值是否為空。 最近一直在學(xué)習(xí)函數(shù)式編程,前面介紹了函數(shù)式編程中非常重要的兩個(gè)運(yùn)算函數(shù)柯里化 和 函數(shù)組合,下文出現(xiàn)的curry 和 compose函數(shù)可以從前兩篇文章中找到。它們都...
摘要:在函數(shù)式編程中數(shù)據(jù)在由純函數(shù)組成的管道中傳遞。函數(shù)式編程中函子是實(shí)現(xiàn)了函數(shù)的容器下文中將函子視為范疇,模型可表示如下但是在函數(shù)式編程中要避免使用這種面向?qū)ο蟮木幊谭绞饺《畬?duì)外暴露了一個(gè)的接口也稱為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會(huì)有 3 篇文章,分別介紹什么是函數(shù)式編程、剖析函數(shù)...
摘要:就像我寫書的過程一樣,每個(gè)開發(fā)者在學(xué)習(xí)函數(shù)式編程的旅程中都會(huì)經(jīng)歷這個(gè)部分。類型在函數(shù)式編程中有一個(gè)巨大的興趣領(lǐng)域類型論,本書基本上完全遠(yuǎn)離了該領(lǐng)域。在函數(shù)式編程中,像這樣涵蓋是很普遍的。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML...
摘要:函數(shù)式編程是聲明式而不是命令式,并且應(yīng)用程序狀態(tài)通過純函數(shù)流轉(zhuǎn)。與面向?qū)ο缶幊滩煌瘮?shù)式編程避免共享狀態(tài),它依賴于不可變數(shù)據(jù)結(jié)構(gòu)和純粹的計(jì)算過程來從已存在的數(shù)據(jù)中派生出新的數(shù)據(jù)。而函數(shù)式編程傾向于復(fù)用一組通用的函數(shù)功能來處理數(shù)據(jù)。 面向?qū)ο缶幊毯兔嫦蜻^程編程都是編程范式,函數(shù)式編程也是一種編程范式,意味著它們都是軟件構(gòu)建的思維方式。與命令式或面向?qū)ο蟠a相比,函數(shù)式代碼傾向于更簡(jiǎn)潔、...
摘要:而純函數(shù),主要強(qiáng)調(diào)相同的輸入,多次調(diào)用,輸出也相同且無副作用。對(duì)于組合可能不返回值的函數(shù)很有用在其它的一些地方,也稱為,也稱為,也稱為 參考文檔1 參考文檔2 函數(shù)式編程術(shù)語 高階函數(shù) Higher-Order Functions 以函數(shù)為參數(shù)的函數(shù) 返回一個(gè)函數(shù)的函數(shù) 函數(shù)的元 Arity 比如,一個(gè)帶有兩個(gè)參數(shù)的函數(shù)被稱為二元函數(shù) 惰性求值 Lazy evaluation 是...
閱讀 1488·2021-10-08 10:04
閱讀 2893·2021-09-22 15:23
閱讀 2867·2021-09-04 16:40
閱讀 1251·2019-08-29 17:29
閱讀 1578·2019-08-29 17:28
閱讀 3067·2019-08-29 14:02
閱讀 2308·2019-08-29 13:18
閱讀 953·2019-08-23 18:35