Partial Application Function trong JS
Partial Application Function là gì? Partial Application Function là tạo ra một hàm mới nhận vào ít đối số đầu vào hơn. Mặc dù định nghĩa cực kì đơn giản, tuy nhiên khái niệm Partial Application Function lại được sử dụng rất rộng rãi trong JS hằng ngày. Chúng ta vẫn luôn tự hỏi ...
Partial Application Function là gì?
Partial Application Function là tạo ra một hàm mới nhận vào ít đối số đầu vào hơn.
Mặc dù định nghĩa cực kì đơn giản, tuy nhiên khái niệm Partial Application Function lại được sử dụng rất rộng rãi trong JS hằng ngày. Chúng ta vẫn luôn tự hỏi bản thân:
Tại sao lại phải áp dụng Partial Application Function trong JS
Câu trả lời đơn giản là
Thứ mà tôi thu được sau khi sử dụng Partial Application Function rõ ràng đẹp đẽ hơn ban đầu và thuần khiết hơn (Functional Purity)
Nguyên lý cơ bản của Partial Application Function là:
Chúng ta tạo ra một method, truyền vào cho nó ít đối số hơn là nó cần. Nó sẽ sinh ra một method mới, method mới này sẽ có nhiệm vụ nhận vào những đối số chưa được truyền vào cho method cha của nó.
Sử dụng Prototype.bind
Cách này không Cool cho lắm nhưng bạn cần biết về nó:
let add = (a, b) => a + b; let increment = add.bind(null, 1); let decrement = add.bind(null, -1); console.log('Increment 3 by 1: ', increment(3)); // 4 console.log('Decrement 3 by 1: ', decrement(3)); // 2
Đoạn code trên có 4 thứ cơ bản:
- Chúng ta tạo 1 method add nhận vào 2 đối số đầu vào
- Chúng ta preloaded nó với 1 tham số và tạo ra một method mới chỉ nhận 1 tham số đầu vào increment
- Chúng ta tiếp tục preload nó với một tham số khác, và lần này cũng tạo ra một method mới chỉ nhận 1 tham số đầu vào là decrement
- Cuối cùng chúng ta gọi các method vừa được tạo ra với duy nhất 1 tham số đầu vào.
Binding giúp chúng ta tạo ra method mới ít dư thừa và chắc chắn hơn. Nhưng nó vẫn chứa một số điểm yếu:
- Quá khó đoán: Function.prototype.bind luôn tạo ra một method mới ngay cả khi chúng ta đưa vào số tham số vượt quá số tham số của method ban đầu. Chúng ta không biết đưa vào bao nhiêu tham số thì đủ.
- Hãy chú ý đến null Chúng ta phải luôn luôn gắn một context method mới vào mỗi khi gọi Function.prototyp.bind, thật sự khá là phiền phức. Not Cool!
Currying
Currying là một kĩ thuật tuyệt vời của Functional Programming được đưa vào JS bằng khả năng Higher Order Functions. Một điểm khá quan trọng là:
Currying không phải là Partial Application Function, nhưng nhờ nó chúng ta có thể tạo ra các Partial Application Function
let add = x => y => x + y; let increment = add(1); let decrement = add(-1); console.log('Increment 3 by 1: ', increment(3)); // 4 console.log('Decrement 3 by 1: ', decrement(3)); // 2
Currying ngon lành hơn binding nhiều !
- Dễ đoán: Currying luôn luôn tạo ra một method mới chỉ nhận duy nhất 1 đối số.
- Tuyệt vời: Currying tạo ra một method mới, method này sẽ ghi nhớ được toàn bộ tham số đã truyền vào trước đó nhớ cơ chế Closure.
- Pure: Nó luôn luôn tạo ra một method giống nhau khi tham số đầu vào là giống nhau (khái niệm cơ bản của Functional Programming)
Functional Purity
Currying luôn tạo ra một method với 1 tham số đầu vào
Currying giống như chiếc thắt lưng bảo hiểm cho Functional Programmer, chúng ta sẽ thấy nó quan trọng như thế nào ngay bây giờ. Chúng ta sẽ xem xét đoạn code dưới đây:
‘Hello’.replace(/Hello/g, ‘Bye’).concat(‘!’);
Chúng ta thấy đoạn code trên vô cùng quen thuộc, nó được gọi là Method Chaining, được sử dụng trong Object Oriented Design, chúng ta sẽ phân tích rõ hơn để thấy cách nó hoạt động cũng như điểm yếu của nó. 'Hello' được truyền vào một chuỗi các method, chúng ta có thể nối thêm vô số method vào nó nữa, chỉ cần đáp ứng một điều kiện là: các method này phù hợp với các method đã được nối vào trước đó. Chúng ta có thể sử dụng như sau:
‘Hello’ .replace(/Hello/g, ‘Bye’) .concat(‘!’) .repeat(2) .split('!') .filter(x=>x!='!') .map(x=>'Hello').toString();
Đoạn code trên chạy được là vì chúng ta đã truyền cho nó một object 'Hello', và có một sự thật hiển nhiên là các method dưới sẽ không thể hoạt động nếu chúng ta không truyền một object thích hợp vào cho nó. Đây cũng là nguyên lý cơ bản của Object Oriented Design
Tất cả mọi thứ phụ thuộc vào data
Chúng ta hãy functional hóa nó như sau:
const replace = (regex, replacement, str) => str.replace(regex, replacement); const concat = (item, str) => str.concat(item); concat('!',replace(/Hello/g,'Bye','Hello'));
Có vẻ hơi khó đọc, nhưng đừng lo, nó là Functional Programming, trong đoạn code trên chúng ta đã sử dụng Function Composion trong Functional Programming
Functional Composion là việc chúng ta kết hợp 2 hay nhiều function lại để tạo ra một function mới.
Vậy chúng ta sẽ cần một Composer function để kết hợp replace và concat lại với nhau:
const compose = (...fns) => x => fns.reduce((v, fn) => fn(v), x);
Nhưng khoan!, composer của chúng ta chỉ nhận vào một tham số, nhưng replace, concat của chúng ta nhận vào 2 tham số. Đây là nơi mà chúng ta sẽ sử dụng Currying:
const replace = (regex, replacement) => str => str.repalace(regex, replacement); const concat = item => str => str.concat(item);
Và kết quả của chúng ta:
compose(replace(/Hello/g,’Bye’),concat(‘!’))(‘Hello’);
Hoặc có thể là như thế này:
compose( replace(/Hello/g,’Bye’), concat(‘!’), repeat(2), split('!'), filter(x=>x!='!'), map(x=>'Hello'), toString )(‘Hello’)
hoặc là
processHello(‘Hello’)
Và bạn hãy để ý: Có bất kì dấu . nào xuất hiện không? Và kĩ thuật này còn được gọi là: Tacit Programming Tacit Programming có thể được viết rất hiệu quả bằng Currying và Composition. Currying chuẩn bị các function để nhận data truyền vào, và Composition giúp chúng ta trong việc kết hợp các function này.
Cách viết này đảm bảo cho code của chúng ta được tách thành các Pure Functions rất nhỏ, nếu không chúng ta sẽ không thể kết hợp chúng lại.
Hãy tưởng tượng Batman không có thắt lưng, tương tự như việc Functional Programming trong JS thiếu đi Currying. Đó là lý do tại sao mà các lib functional programming nhưng lodash/fp hay Ramda luôn luôn gắn liền với Currying.
Thanks for reading
Source
https://hackernoon.com/partial-application-of-functions-dbe7d9b80760