Partial Application Function trong JS
Bài đăng này đã không được cập nhật trong 3 năm
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ào2
đối số đầu vào - Chúng ta
preloaded
nó với1
tham số và tạo ra một method mới chỉ nhận1
tham số đầu vàoincrement
- 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ận1
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ọiFunction.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ất1
đố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
All rights reserved