摘要:談到函數(shù)式編程時(shí),我們常提到機(jī)制方法,而不是核心原則。函數(shù)式編程不是關(guān)于和這些概念的,雖然它們確實(shí)很有用。從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。我們一起用一些函數(shù)式編程的辦法重構(gòu)這個(gè)函數(shù)吧。
原文作者:@VictorSavkin
原文地址:https://vsavkin.com/functiona...
中文翻譯:文藺
譯文地址:http://www.wemlion.com/2016/f...
本文由@文藺 翻譯,轉(zhuǎn)載請(qǐng)保留此聲明。著作權(quán)屬于原作者,請(qǐng)勿用作商業(yè)用途。
談到函數(shù)式編程時(shí),我們常提到機(jī)制、方法,而不是核心原則。函數(shù)式編程不是關(guān)于 Monad、Monoid 和 Zipper 這些概念的,雖然它們確實(shí)很有用。從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。本文是我在重構(gòu) TypeScript 代碼時(shí)使用函數(shù)式的一些思考的結(jié)果。
首先,我們需要用到以下幾項(xiàng)技術(shù):
盡可能使用函數(shù)代替簡(jiǎn)單值
數(shù)據(jù)轉(zhuǎn)換過程管道化
提取通用函數(shù)
來,開始吧!
假設(shè)我們有兩個(gè)類,Employee 和 Department。Employee 有 name 和 salary 屬性,Department 只是 Employee 的簡(jiǎn)單集合。
class Employee { constructor(public name: string, public salary: number) {} } class Department { constructor(public employees: Employee[]) {} works(employee: Employee): boolean { return this.employees.indexOf(employee) > -1; } }
我們要重構(gòu)的是 averageSalary 函數(shù)。
function averageSalary(employees: Employee[], minSalary: number, department?: Department): number { let total = 0; let count = 0; employees.forEach((e) => { if(minSalary <= e.salary && (department === undefined || department.works(e))){ total += e.salary; count += 1; } }); return total === 0 ? 0 : total / count; }
averageSalary 函數(shù)接收 employee 數(shù)組、最低薪資 minSalary 以及可選的 department 作為參數(shù)。如果傳了 department 參數(shù),函數(shù)會(huì)計(jì)算該部門中所有員工的平均薪資;若不傳,則對(duì)全部員工進(jìn)行計(jì)算。
該函數(shù)的使用方式如下:
describe("average salary", () => { const empls = [ new Employee("Jim", 100), new Employee("John", 200), new Employee("Liz", 120), new Employee("Penny", 30) ]; const sales = new Department([empls[0], empls[1]]); it("calculates the average salary", () => { expect(averageSalary(empls, 50, sales)).toEqual(150); expect(averageSalary(empls, 50)).toEqual(140); }); });
需求雖簡(jiǎn)單粗暴,可就算不提代碼難以拓展,其混亂是顯而易見的。若新增條件,函數(shù)簽名及接口就不得不發(fā)生變動(dòng),if 語句也會(huì)也越來越臃腫可怕。
我們一起用一些函數(shù)式編程的辦法重構(gòu)這個(gè)函數(shù)吧。
使用函數(shù)代替簡(jiǎn)單值使用函數(shù)代替簡(jiǎn)單值看起來似乎不太直觀,但這卻是整理歸納代碼的強(qiáng)大辦法。在我們的例子中,這樣做,意味著要將 minSalary 和 department 參數(shù)替換成兩個(gè)條件檢驗(yàn)的函數(shù)。
type Predicate = (e: Employee) => boolean; function averageSalary(employees: Employee[], salaryCondition: Predicate, departmentCondition?: Predicate): number { let total = 0; let count = 0; employees.forEach((e) => { if(salaryCondition(e) && (departmentCondition === undefined || departmentCondition(e))){ total += e.salary; count += 1; } }); return total === 0 ? 0 : total / count; } // ... expect(averageSalary(empls, (e) => e.salary > 50, (e) => sales.works(e))).toEqual(150);
我們所做的就是將 salary、department 兩個(gè)條件接口統(tǒng)一起來。而此前這兩個(gè)條件是寫死的,現(xiàn)在它們被明確定義了,并且遵循一致的接口。這次整合允許我們將所有條件作為數(shù)組傳遞。
function averageSalary(employees: Employee[], conditions: Predicate[]): number { let total = 0; let count = 0; employees.forEach((e) => { if(conditions.every(c => c(e))){ total += e.salary; count += 1; } }); return (count === 0) ? 0 : total / count; } //... expect(averageSalary(empls, [(e) => e.salary > 50, (e) => sales.works(e)])).toEqual(150);
條件數(shù)組只不過是組合的條件,可以用一個(gè)簡(jiǎn)單的組合器將它們放到一起,這樣看起來更加明晰。
function and(predicates: Predicate[]): Predicate { return (e) => predicates.every(p => p(e)); } function averageSalary(employees: Employee[], conditions: Predicate[]): number { let total = 0; let count = 0; employees.forEach((e) => { if(and(conditions)(e)){ total += e.salary; count += 1; } }); return (count == 0) ? 0 : total / count; }
值得注意的是,“and” 組合器是通用的,可以復(fù)用并且還可能拓展為庫。
提起結(jié)果
現(xiàn)在,averageSalary 函數(shù)已健壯得多了。我們可以加入新條件,無需破壞函數(shù)接口或改變函數(shù)實(shí)現(xiàn)。
數(shù)據(jù)轉(zhuǎn)換過程管道化函數(shù)式編程的另外一個(gè)很有用的實(shí)踐是將所有數(shù)據(jù)轉(zhuǎn)換過程變成管道。在本例中,就是將 filter 過程提取到循環(huán)外面。
function averageSalary(employees: Employee[], conditions: Predicate[]): number { const filtered = employees.filter(and(conditions)); let total = 0 let count = 0 filtered.forEach((e) => { total += e.salary; count += 1; }); return (count == 0) ? 0 : total / count; }
這樣一來計(jì)數(shù)的 count 就沒什么用了。
function averageSalary(employees: Employee[], conditions: Predicate[]): number{ const filtered = employees.filter(and(conditions)); let total = 0 filtered.forEach((e) => { total += e.salary; }); return (filtered.length == 0) ? 0 : total / filtered.length; }
接下來,如在疊加之前將 salary 摘取出來,求和過程就變成簡(jiǎn)單的 reduce 了。
function averageSalary(employees: Employee[], conditions: Predicate[]): number { const filtered = employees.filter(and(conditions)); const salaries = filtered.map(e => e.salary); const total = salaries.reduce((a,b) => a + b, 0); return (salaries.length == 0) ? 0 : total / salaries.length; }提取通用函數(shù)
接著我們發(fā)現(xiàn),最后兩行代碼和當(dāng)前域完全沒什么關(guān)系。其中不包含任何與員工、部門相關(guān)的信息。僅僅只是一個(gè)計(jì)算平均數(shù)的函數(shù)。所以也將其提取出來。
function average(nums: number[]): number { const total = nums.reduce((a,b) => a + b, 0); return (nums.length == 0) ? 0 : total / nums.length; } function averageSalary(employees: Employee[], conditions: Predicate[]): number { const filtered = employees.filter(and(conditions)); const salaries = filtered.map(e => e.salary); return average(salaries); }
又一次,提取出的函數(shù)是完全通用的。
最后,將所有 salary 部分提出來之后,我們得到終極方案。
function employeeSalaries(employees: Employee[], conditions: Predicate[]): number[] { const filtered = employees.filter(and(conditions)); return filtered.map(e => e.salary); } function averageSalary(employees: Employee[], conditions: Predicate[]): number { return average(employeeSalaries(employees, conditions)); }
對(duì)比原始方案和終極方案,我敢說,毫無疑問,后者更棒。首先,它更通用(我們可以不破壞函數(shù)接口的情況下添加新類型的判斷條件)。其次,我們從可變狀態(tài)(mutable state)和 if 語句中解脫出來,這使代碼更容易閱讀、理解。
何時(shí)收手函數(shù)式風(fēng)格的編程中,我們會(huì)編寫許多小型函數(shù),它們接收一個(gè)集合,返回新的集合。這些函數(shù)能夠以不同方式組合、復(fù)用 —— 棒極了。不過,這種風(fēng)格的一個(gè)缺點(diǎn)是代碼可能會(huì)變得過度抽象,導(dǎo)致難以讀懂,那些函數(shù)組合在一起到底要干嘛?
我喜歡使用樂高來類比:樂高積木能夠以不同形式放在一起 —— 它們是可組合的。但注意,并不是所有積木都是一小塊。所以,在使用本文所述技巧進(jìn)行代碼重構(gòu)時(shí),千萬別妄圖將一切都變成接收數(shù)組、返回?cái)?shù)組的函數(shù)。誠(chéng)然,這樣一些函數(shù)組合使用極度容易,可它們也會(huì)顯著降低我們對(duì)程序的理解能力。
小結(jié)本文展示了如何使用函數(shù)式思維重構(gòu) TypeScript 代碼。我所遵循的是以下幾點(diǎn)規(guī)則:
盡可能使用函數(shù)代替簡(jiǎn)單值
數(shù)據(jù)轉(zhuǎn)換過程管道化
提取通用函數(shù)
了解更多強(qiáng)烈推薦以下兩本書:
“JavaScript Allonge” by Reginald Braithwaite
“Functional JavaScript” by Michael Fogus
關(guān)注 @victorsavkin 獲得更多關(guān)于 Angular 和 TypeScript 的知識(shí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80536.html
摘要:感謝來自團(tuán)隊(duì)的發(fā)起的,以及核心團(tuán)隊(duì)成員和的改進(jìn)和審查。在中,我們已經(jīng)發(fā)布了一個(gè)與環(huán)境無關(guān)的版本,可以在瀏覽器或純引擎中使用。同樣,我們建議你查看完整的發(fā)布說明從而了解其他的改進(jìn),包括,,,等。 原文:Vue 2.5 released 譯者:neal1991 welcome to star my articles-translator , providing you advanced ...
摘要:在這篇文章中,我將告訴你如何用編寫自定義指令。中的自定義指令讓我們來創(chuàng)建一個(gè)只為任何的塊,小部件或者人名在右邊添加標(biāo)題,子標(biāo)題和文本的指令。另外,設(shè)置了指令的使用級(jí)別給元素和屬性,分別使用和表示。 原文鏈接 : How to write custom AngularJS Directive using TypeScript?原文作者 : Siddharth Pandey譯者 : 李林璞...
摘要:致力于為應(yīng)用提供一個(gè)類型安全表達(dá)力強(qiáng)可組合的狀態(tài)管理方案。是一組的命名空間。是內(nèi)置組件的鏡像,但允許組件的額外接受類型的數(shù)據(jù)。這次內(nèi)容更新,是由組件處理的。這些小的組件不必知道所有的應(yīng)用狀態(tài)數(shù)據(jù)。這是因?yàn)榇蟛糠謱?duì)的研究來自于。 Focal Focal 致力于為 React 應(yīng)用提供一個(gè)類型安全、表達(dá)力強(qiáng)、可組合的狀態(tài)管理方案。 用一個(gè)不可變的 (immutable) 、響應(yīng)式的 (o...
摘要:原文是一門編譯到的強(qiáng)類型靜態(tài)類型語言它的功能受到的激發(fā)并且使用編寫其目標(biāo)是編譯到同時(shí)保持清潔跟可讀而且根據(jù)作者所說具備跟其他編譯到的語言相互操作的能力繼承了當(dāng)中一些突出的功能其中有類型推斷允許在任何可能的地方減少類型聲明的書寫一種類型構(gòu) 原文: http://www.infoq.com/news/2014/09/purescript-haskell-javascript PureS...
摘要:在這篇文章里,我將介紹如何使用去編寫的。一個(gè)新的子將被創(chuàng)建并作為變量注入到的構(gòu)造函數(shù)當(dāng)中。當(dāng)使用一個(gè)構(gòu)造函數(shù)就可以很好地解決問題,因?yàn)楹瘮?shù)提升起到了很重要的作用。自定義接口類型接著就可以在構(gòu)造器使用參數(shù)獲得強(qiáng)類型和智能支持了。 原文鏈接 : How to write AngularJS controller using TypeScript?原文作者 : Siddharth Pande...
閱讀 2684·2021-11-23 09:51
閱讀 2427·2021-09-30 09:48
閱讀 2060·2021-09-22 15:24
閱讀 1024·2021-09-06 15:02
閱讀 3333·2021-08-17 10:14
閱讀 1956·2021-07-30 18:50
閱讀 1992·2019-08-30 15:53
閱讀 3192·2019-08-29 18:43