+8

Functional Programming trong Javascript (Phần 2)

Tiếp nối câu chuyện tìm hiểu Functional Programming trong Javascript ở phần 1 thì tất nhiên hôm nay sẽ là phần 2 của chủ đề trên. Cũng không cần mở đầu dài dòng như lần trước. Chúng ta bắt đầu tiếp với những khái niệm còn lại.

Các khái niệm

Referential transparency

Giả sử chúng ta có một hàm tính diện tích của hình vuông như sau:

const square = (n) => n * n;

Nếu ta truyền giá trị n = 2 vào hàm trên thì kết quả nhận được luôn là giá trị 4. Điều đó có nghĩa là chúng ta có thể thay thế square(2) bằng giá trị 4. Hàm như trên được gọi là referentially transparent. Chúng ta có thể đơn giản hiểu nó là như sau:

pure functions + immutable data = referential transparency

Với một ví dụ khác:

const sum = (a, b) => a + b;

Sử dụng hàm với các tham số truyền vào như sau:

sum(3, sum(5, 8));

Với giá trị của sum(5, 8) luôn là 13 chúng ta có thể viết lại như sau:

sum(3, 13);

Và biểu thức trên vẫn luôn luôn cho ta kết quả 16.

Functions as first-class entities

Ý tưởng cho khái niệm này chính là việc coi các hàm là giá trị và được sử dụng làm dữ liệu truyền vào hàm khác.

  • Đề cập đến nó từ hằng và biến.
  • Truyền nó vào như là một tham số cho các hàm khác.
  • Trả về kết quả từ hàm khác.

Bằng cách này chúng ta có thể kết hợp các hàm khác nhau để tạo nên hàm mới với hành vi mới. Với một hàm thực hiện chức năng tính tổng 2 số và sau đó thực hiện nhân đôi giá trị như sau:

const doubleSum = (a, b) => (a + b) * 2;

Với một hàm gần giống như trên, nhưng thay vì cộng 2 số chúng ta thực hiện trừ như sau:

const doubleSubtraction = (a, b) => (a - b) * 2;

Các hàm ở trên đều có logic tương tự nhau, chúng chỉ khác nhau ở toán tử sử dụng. Nếu chúng ta coi các hàm là các giá trị và sử dụng nó làm tham số. Từ đây chúng ta có thể tự định nghĩa một hàm mà nhận hàm toán tử như sau:

const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;

const doubleOperator = (f, a, b) => f(a, b) * 2;

doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4

Chúng ta có 1 đối số là f và sử dụng nó để xử lý ab. Để sử dụng, ta truyền vào hàm sumsubtraction để có kết quả mong muốn.

Higher-order functions

Để nói về higher-order functions chúng ta sẽ nghĩ đến:

  • Nhận 1 hoặc nhiều hàm làm tham số truyền vào
  • Kết quả trả về là một hàm

Ở ví dụ của phần trên là hàm doubleOperator chính là điển hình cho higher-order functions vì nó lấy hàm toán tử làm đối số và sử dụng nó. Chúng ta thử xem một vài ví dụ trong JS như dưới đây.

Filter

Đây chính là tính năng dùng với một collection cho trước và ta muốn lọc kết quả theo một thuộc tính có ở trong collection. Một ví dụ là ta có 1 danh sách các số nguyên và muốn lấy ra những số chẵn.

Với một suy nghĩ không có hàm nào sẵn để có thể thực hiện yêu cầu trên thì chúng ra sẽ thử cách như sau:

  • Tạo một mảng rỗng tên là evenNumbers.
  • Chạy vòng lặp qua từng phần tử của mãng cho trước.
  • Thêm các số chẵn vào mảng evenNumbers.
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 == 0) {
    evenNumbers.push(numbers[i]);
  }
}

console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]

Nhưng mà có filter là higher order function rồi thì cần gì khổ như trên nhỉ. Nhẹ nhàng như sau là thoản mãn cái mình cần rồi.

const even = n => n % 2 == 0;
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]

Chúng ta cũng có thể sử dụng để lọc với mảng các đối tượng.

let people = [
  { name: "A", age: 26 },
  { name: "B", age: 10 },
  { name: "C", age: 30 }
];

Chúng ta muốn tìm những người trên 21 tuổi như sau:

const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'A', age: 26 }, { name: 'C', age: 30 }]

Map

Ý tưởng của map chính là làm thay đổi một collection. Phương thức map làm thay đổi một collection bằng cách áp dụng một hàm cho tất cả các phần tử của nó và tạo một collection mới từ các giá trị được trả về.

Vẫn với collection là people ở trên. Nhưng bây giờ chúng ta muốn một collection với nội dung như là A is 26 years old

let people = [
  { name: "A", age: 26 },
  { name: "B", age: 10 },
  { name: "C", age: 30 }
];

let peopleSentences = [];

for (let i = 0; i < people.length; i++) {
  let sentence = people[i].name + " is " + people[i].age + " years old";
  peopleSentences.push(sentence);
}

console.log(peopleSentences); // ['A is 26 years old', 'B is 10 years old', 'C is 30 years old']

Để có một kết quả tương tự thì chúng ta có thể dùng map như sau:

const makeSentence = (person) => `${person.name} is ${person.age} years old`;

const peopleSentences = (people) => people.map(makeSentence);
  
peopleSentences(people);

Kết luận lần 2

Đến đây thì mình đã giới thiệu được một số khái niêm của Functional programming trong Javascript. Tuy nhiên, mình vẫn chưa nhắc đến cái gọi là lodash/fp mà từ đầu mình thắc mắc. Vì vậy, mong rằng bài tiếp theo mình sẽ giới thiệu về nó trong chuỗi bài về Functional programming. Cảm ơn mọi người đã theo dõi bài viết. Có gì thiếu sót mong bạn bỏ qua 🙇♀ .


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí