成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

[譯] 函數式TypeScript

開發 前端
談到函數式編程時,我們常提到機制、方法,而不是核心原則。函數式編程不是關于 Monad、Monoid 和 Zipper 這些概念的,雖然它們確實很有用。從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。本文是我在重構 TypeScript 代碼時使用函數式的一些思考的結果。

[[172673]]

談到函數式編程時,我們常提到機制、方法,而不是核心原則。函數式編程不是關于 Monad、Monoid 和 Zipper 這些概念的,雖然它們確實很有用。從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。本文是我在重構 TypeScript 代碼時使用函數式的一些思考的結果。

首先,我們需要用到以下幾項技術:

  • 盡可能使用函數代替簡單值
  • 數據轉換過程管道化
  • 提取通用函數

來,開始吧!

假設我們有兩個類,Employee 和 Department。Employee 有 name 和 salary 屬性,Department 只是 Employee 的簡單集合。

  1. class Employee { 
  2.   constructor(public name: string, public salary: number) {} 
  3.  
  4. class Department { 
  5.   constructor(public employees: Employee[]) {} 
  6.  
  7.   works(employee: Employee): boolean { 
  8.     return this.employees.indexOf(employee) > -1; 
  9.   } 
  10.  

我們要重構的是 averageSalary 函數。

  1. function averageSalary(employees: Employee[], minSalary: number, department?: Department): number { 
  2.    let total = 0; 
  3.    let count = 0; 
  4.  
  5.    employees.forEach((e) => { 
  6.      if(minSalary <= e.salary && (department === undefined || department.works(e))){ 
  7.        total += e.salary; 
  8.        count += 1; 
  9.      } 
  10.    }); 
  11.  
  12.   return total === 0 ? 0 : total / count
  13.  }  

averageSalary 函數接收 employee 數組、***薪資 minSalary 以及可選的 department 作為參數。如果傳了 department 參數,函數會計算該部門中所有員工的平均薪資;若不傳,則對全部員工進行計算。

該函數的使用方式如下: 

  1. describe("average salary", () => { 
  2.   const empls = [ 
  3.     new Employee("Jim", 100), 
  4.     new Employee("John", 200), 
  5.     new Employee("Liz", 120), 
  6.     new Employee("Penny", 30) 
  7.   ]; 
  8.  
  9.   const sales = new Department([empls[0], empls[1]]); 
  10.  
  11.   it("calculates the average salary", () => { 
  12.     expect(averageSalary(empls, 50, sales)).toEqual(150); 
  13.     expect(averageSalary(empls, 50)).toEqual(140); 
  14.   }); 
  15. });  

需求雖簡單粗暴,可就算不提代碼難以拓展,其混亂是顯而易見的。若新增條件,函數簽名及接口就不得不發生變動,if 語句也會也越來越臃腫可怕。

我們一起用一些函數式編程的辦法重構這個函數吧。

使用函數代替簡單值

使用函數代替簡單值看起來似乎不太直觀,但這卻是整理歸納代碼的強大辦法。在我們的例子中,這樣做,意味著要將 minSalary 和 department 參數替換成兩個條件檢驗的函數。 

  1. type Predicate = (e: Employee) => boolean; 
  2.  
  3. function averageSalary(employees: Employee[], salaryCondition: Predicate, 
  4.   departmentCondition?: Predicate): number { 
  5.   let total = 0; 
  6.   let count = 0; 
  7.  
  8.   employees.forEach((e) => { 
  9.     if(salaryCondition(e) && (departmentCondition === undefined || departmentCondition(e))){ 
  10.       total += e.salary; 
  11.       count += 1; 
  12.     } 
  13.   }); 
  14.  
  15.   return total === 0 ? 0 : total / count
  16.  
  17. // ... 
  18.  
  19. expect(averageSalary(empls, (e) => e.salary > 50, (e) => sales.works(e))).toEqual(150);  

我們所做的就是將 salary、department 兩個條件接口統一起來。而此前這兩個條件是寫死的,現在它們被明確定義了,并且遵循一致的接口。這次整合允許我們將所有條件作為數組傳遞。 

  1. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  2.   let total = 0; 
  3.   let count = 0; 
  4.  
  5.   employees.forEach((e) => { 
  6.     if(conditions.every(c => c(e))){ 
  7.       total += e.salary; 
  8.       count += 1; 
  9.     } 
  10.   }); 
  11.   return (count === 0) ? 0 : total / count
  12.  
  13. //... 
  14.  
  15. expect(averageSalary(empls, [(e) => e.salary > 50, (e) => sales.works(e)])).toEqual(150);  

條件數組只不過是組合的條件,可以用一個簡單的組合器將它們放到一起,這樣看起來更加明晰。 

  1. function and(predicates: Predicate[]): Predicate { 
  2.   return (e) => predicates.every(p => p(e)); 
  3.  
  4. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  5.   let total = 0; 
  6.   let count = 0; 
  7.  
  8.   employees.forEach((e) => { 
  9.     if(and(conditions)(e)){ 
  10.       total += e.salary; 
  11.       count += 1; 
  12.     } 
  13.   }); 
  14.   return (count == 0) ? 0 : total / count
  15.  

值得注意的是,“and” 組合器是通用的,可以復用并且還可能拓展為庫。

提起結果

現在,averageSalary 函數已健壯得多了。我們可以加入新條件,無需破壞函數接口或改變函數實現。

數據轉換過程管道化

函數式編程的另外一個很有用的實踐是將所有數據轉換過程變成管道。在本例中,就是將 filter 過程提取到循環外面。 

  1. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  2.   const filtered = employees.filter(and(conditions)); 
  3.  
  4.   let total = 0 
  5.   let count = 0 
  6.  
  7.   filtered.forEach((e) => { 
  8.     total += e.salary; 
  9.     count += 1; 
  10.   }); 
  11.  
  12.   return (count == 0) ? 0 : total / count
  13.  

這樣一來計數的 count 就沒什么用了。 

  1. function averageSalary(employees: Employee[], conditions: Predicate[]): number{ 
  2.   const filtered = employees.filter(and(conditions)); 
  3.  
  4.   let total = 0 
  5.   filtered.forEach((e) => { 
  6.     total += e.salary; 
  7.   }); 
  8.  
  9.   return (filtered.length == 0) ? 0 : total / filtered.length; 
  10.  

接下來,如在疊加之前將 salary 摘取出來,求和過程就變成簡單的 reduce 了。 

  1. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  2.   const filtered = employees.filter(and(conditions)); 
  3.   const salaries = filtered.map(e => e.salary); 
  4.  
  5.   const total = salaries.reduce((a,b) => a + b, 0); 
  6.   return (salaries.length == 0) ? 0 : total / salaries.length; 
  7.  

提取通用函數

接著我們發現,***兩行代碼和當前域完全沒什么關系。其中不包含任何與員工、部門相關的信息。僅僅只是一個計算平均數的函數。所以也將其提取出來。 

  1. function average(nums: number[]): number { 
  2.   const total = nums.reduce((a,b) => a + b, 0); 
  3.   return (nums.length == 0) ? 0 : total / nums.length; 
  4.  
  5. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  6.   const filtered = employees.filter(and(conditions)); 
  7.   const salaries = filtered.map(e => e.salary); 
  8.   return average(salaries); 
  9.  

又一次,提取出的函數是完全通用的。

***,將所有 salary 部分提出來之后,我們得到***方案。 

  1. function employeeSalaries(employees: Employee[], conditions: Predicate[]): number[] { 
  2.   const filtered = employees.filter(and(conditions)); 
  3.   return filtered.map(e => e.salary); 
  4.  
  5. function averageSalary(employees: Employee[], conditions: Predicate[]): number { 
  6.   return average(employeeSalaries(employees, conditions)); 
  7.  

對比原始方案和***方案,我敢說,毫無疑問,后者更棒。首先,它更通用(我們可以不破壞函數接口的情況下添加新類型的判斷條件)。其次,我們從可變狀態(mutable state)和 if 語句中解脫出來,這使代碼更容易閱讀、理解。

何時收手

函數式風格的編程中,我們會編寫許多小型函數,它們接收一個集合,返回新的集合。這些函數能夠以不同方式組合、復用 —— 棒極了。不過,這種風格的一個缺點是代碼可能會變得過度抽象,導致難以讀懂,那些函數組合在一起到底要干嘛?

我喜歡使用樂高來類比:樂高積木能夠以不同形式放在一起 —— 它們是可組合的。但注意,并不是所有積木都是一小塊。所以,在使用本文所述技巧進行代碼重構時,千萬別妄圖將一切都變成接收數組、返回數組的函數。誠然,這樣一些函數組合使用極度容易,可它們也會顯著降低我們對程序的理解能力。

小結

本文展示了如何使用函數式思維重構 TypeScript 代碼。我所遵循的是以下幾點規則:

  • 盡可能使用函數代替簡單值
  • 數據轉換過程管道化
  • 提取通用函數

了解更多

強烈推薦以下兩本書:

關注 @victorsavkin 獲得更多關于 Angular 和 TypeScript 的知識。

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2016-10-11 13:32:50

函數式TypeScriptJavascript

2016-11-04 13:00:55

Asynces6Javascript

2010-01-28 14:51:24

Scala后函數式

2022-01-04 19:21:04

函數TypeScript重載

2023-05-16 16:03:10

2022-02-25 09:19:32

TypeScript輔助函數枚舉

2023-04-14 15:44:20

TypeScrip函數重載

2019-09-09 11:40:18

編程函數開發

2013-09-09 09:41:34

2023-08-24 16:24:44

TypeScript

2017-06-08 14:25:46

Kotlin函數

2012-03-14 10:09:51

ibmdw

2022-07-07 09:03:36

Python返回函數匿名函數

2014-09-05 10:15:41

函數式編程

2013-07-09 09:43:04

函數式思維函數式編程編程

2011-08-24 09:13:40

編程

2023-12-14 15:31:43

函數式編程python編程

2022-09-22 08:19:26

WebFlux函數式編程

2016-10-31 20:46:22

函數式編程Javascript

2011-03-08 15:47:32

函數式編程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久综合久久久 | 国户精品久久久久久久久久久不卡 | 日韩精品人成在线播放 | 日韩av成人在线 | 日韩精品在线播放 | 天天综合国产 | 中文在线播放 | 国产欧美一区二区三区另类精品 | 免费观看国产视频在线 | 国产精品不卡视频 | 精品久久久久国产免费第一页 | 欧美国产精品一区二区 | 99精品欧美一区二区蜜桃免费 | 欧美一区两区 | 888久久久 | 国偷自产av一区二区三区 | 国产乱码久久久久久一区二区 | 天天操精品视频 | 91视频在线 | 亚洲免费网 | 狠狠色综合网站久久久久久久 | 日韩视频区 | 久草久| 国产精品久久久久久久久久久久久 | 精品久久久久久中文字幕 | 国产精品久久久久久亚洲调教 | 99久9| 国产一区二区三区色淫影院 | 精品一二三区 | 欧美精品综合在线 | 欧美一区二区三区大片 | 九九久久在线看 | 国产精品不卡 | 青青草这里只有精品 | 黄色网址免费在线观看 | 羞视频在线观看 | 在线精品一区 | 色爱综合网 | 精品久久一区 | 久久亚洲一区二区三区四区 | 精品久久精品 |