摘要:而且我們不需要了解的特別深,函數式編程很多概念是從范疇論映射過來的。了解范疇論相關概念有助于我們理解函數式編程。函子函子是用來將兩個范疇關聯起來的。
在前面幾篇介紹了函數式比較重要的一些概念和如何用函數組合去解決相對復雜的邏輯。是時候開始介紹如何控制副作用了。
數據類型我們來看看上一篇最后例子:
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!");
這里其實reverseWords還是很難閱讀,你不知道他入參是啥,返回值又是啥。你如果不去看一下代碼,一開始在使用他的時候,你應該是比較害怕的。 “我是不是少傳了一個參數?是不是傳錯了參數?返回值真的一直都是一個字符串嗎?”。這也是類型系統的重要性了,在不斷了解函數式后,你會發現,函數式編程和類型是密切相關的。如果在這里reverseWords的類型明確給出,就相當于文檔了。
但是,JavaScript是動態類型語言,我們不會去明確的指定類型。不過我們可以通過注釋的方式加上類型:
// reverseWords: string => string const reverseWords = compose(join(""), reverse, split(""))
上面就相當于指定了reverseWords是一個接收字符串,并返回字符串的函數。
JS 本身不支持靜態類型檢測,但是社區有很多JS的超集是支持類型檢測的,比如Flow還有TypeScript。當然類型檢測不光是上面所說的自文檔的好處,它還能在預編譯階段提前發現錯誤,能約束行為等。
當然我的后續文章還是以JS為語言,但是會在注釋里面加上類型。
范疇論相關概念范疇論其實并不是特別難,不過是些抽象點的概念。而且我們不需要了解的特別深,函數式編程很多概念是從范疇論映射過來的。了解范疇論相關概念有助于我們理解函數式編程。另外,相信我,只要你小學初中學過一元函數和集合,看懂下面的沒有問題。
定義范疇的定義:
一組對象,是需要操作的數據的一個集合
一組態射,是數據對象上的映射關系,比如 f: A -> B
態射組合,就是態射能夠幾個組合在一起形成一個新的態射
圖片出處:https://en.wikipedia.org/wiki...
一個簡單的例子,上圖來自維基百科。上面就是一個范疇,一共有3個數據對象A,B,C,然后f和g是態射,而gof是一組態射組合。是不是很簡單?
其中態射可以理解是函數,而態射的組合,我們可以理解為函數的組合。而里面的一組對象,不就是一個具有一些相同屬性的數據集嘛。
函子(functor)函子是用來將兩個范疇關聯起來的。
圖片出處:https://ncatlab.org/nlab/show...
對應上圖,比如對于范疇 C 和 D ,函子 F : C => D 能夠:將 C 中任意對象X 轉換為 D 中的 F(X); 將 C 中的態射 f : X => Y 轉換為 D 中的 F(f) : F(X) => F(Y)。你可以發現函子可以:
轉換對象
轉換態射
構建一個函子(functor) Container正如上面所說,函子能夠關聯兩個范疇。而范疇里面必然是有一組數據對象的。這里引入Container,就是為了引入數據對象:
class Container { constructor (value) { this.$value = value } // (value) => Container(value) static of(value) { return new Container(value) } }
我們聲明了一個Container的類,然后給了一個靜態的of方法用于去生成這個Container的實例。這個of其實還有個好聽的名字,賣個關子,后面介紹。
我們來看一下使用這個Container的例子:
// Container(123) Container.of(123) // Container("Hello Conatiner!") Container.of("Hello Conatiner!") // Container(Conatiner("Test !")) Container.of(Container.of("Test !"))
正如上面看到的,Container是可以嵌套的。我們仔細看一下這個Contaienr:
$value的類型不確定,但是一旦賦值之后,類型就確定了
一個Conatiner只會有一個value
我們雖然能直接拿到$value,但是不要這樣做,不然我們要個container干啥呢
第一個functor讓我們回看一下定義,函子是用來將兩個范疇關聯起來的。所以我們還需要一個態射(函數)去把兩個范疇關聯起來:
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是啥的看第二篇文章。
你可能會說:“哦,這是你說的functor,那又有啥用呢?”。接下來,就講一個應用。
不過再講應用前先講一下這個of,其實上面這種functor,叫做pointed functor, ES5里面的Array就應用了這種模式:Array.of。他是一種模式,不僅僅是用來省略構建對象的new關鍵字的。我感覺和scala里面的compaion object有點類似。
Maybe type在現實的代碼中,存在很多數據是可選的,返回的數據可能是存在的也可能不存在:
type Person = { info?: { age?: string } }
上面是flow里面的類型聲明,其中?代表這個數據可能存在,可能不存在。我相信像上面的數據結構,你在接收后端返回的數據的時候經常遇到。假如我們要取這個age屬性,我們通常是怎么處理的呢?
當然是加判斷啦:
const person = { info: {} } const getAge = (person) => { return person && person.info && person.info.age } getAge(person) // undefined
你會發現為了取個age,我們需要加很多的判斷。當數據中有很多是可選的數據,你會發現你的代碼充滿了這種類型判斷。心累不?
Okey,Maybe type就是為了解決這個問題的,先讓我們實現一個:
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 } } }
應用一波:
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
來復盤一波,上面的map依然是一個functor(函子)。不過呢,在做類型轉換的時候加上了邏輯:
map(fn) { return this.isNothing ? this : Maybe.of(fn(this.$value)); }
所以也就是上面的轉換關系可以表示為:
其實一看圖就出來了,“哦,你把判斷移動到了map里面。有啥用?”。ok,羅列一下好處:
更安全
將判斷邏輯進行封裝,代碼更簡潔
聲明式代碼,沒有各種各樣的判斷
其實,不確定性,也是一種副作用。對于可選的數據,我們在運行時是很難確定他的真實的數據類型的,我們用Maybe封裝一下其實本身就是封裝這種不確定性。這樣就能保證我們的一個入參只有可能會返回一種輸出了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98777.html
摘要:函子上面容器上定義了方法,的定義也類似是實現了函數并遵守一些特定規則的容器類型。不同類型的函子容器在處理內部值時,經常遇到傳入參數異常的情況的情況,檢查值的合理性就非常重要。函子保證在調用傳入的函數之前,檢查值是否為空。 最近一直在學習函數式編程,前面介紹了函數式編程中非常重要的兩個運算函數柯里化 和 函數組合,下文出現的curry 和 compose函數可以從前兩篇文章中找到。它們都...
摘要:在函數式編程中數據在由純函數組成的管道中傳遞。函數式編程中函子是實現了函數的容器下文中將函子視為范疇,模型可表示如下但是在函數式編程中要避免使用這種面向對象的編程方式取而代之對外暴露了一個的接口也稱為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會有 3 篇文章,分別介紹什么是函數式編程、剖析函數...
摘要:就像我寫書的過程一樣,每個開發者在學習函數式編程的旅程中都會經歷這個部分。類型在函數式編程中有一個巨大的興趣領域類型論,本書基本上完全遠離了該領域。在函數式編程中,像這樣涵蓋是很普遍的。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML...
摘要:而純函數,主要強調相同的輸入,多次調用,輸出也相同且無副作用。對于組合可能不返回值的函數很有用在其它的一些地方,也稱為,也稱為,也稱為 參考文檔1 參考文檔2 函數式編程術語 高階函數 Higher-Order Functions 以函數為參數的函數 返回一個函數的函數 函數的元 Arity 比如,一個帶有兩個參數的函數被稱為二元函數 惰性求值 Lazy evaluation 是...
閱讀 1377·2021-10-08 10:04
閱讀 2681·2021-09-22 15:23
閱讀 2724·2021-09-04 16:40
閱讀 1172·2019-08-29 17:29
閱讀 1492·2019-08-29 17:28
閱讀 2988·2019-08-29 14:02
閱讀 2221·2019-08-29 13:18
閱讀 838·2019-08-23 18:35