Giải thích về lập trình chức năng JavaScript: Ứng dụng một phần và làm xoăn
Với việc áp dụng thư viện Redux JavaScript, chuỗi công cụ và tiện ích mở rộng cú pháp Lý do , và khung JavaScript chu kỳ , lập trình chức năng với JavaScript ngày càng trở nên phù hợp. Hai ý tưởng quan trọng có nguồn root từ suy nghĩ hàm là currying , biến một hàm của nhiều đối số thành một chuỗi các lệnh gọi hàm và ứng dụng từng phần , cố định giá trị của một số đối số của hàm mà không đánh giá đầy đủ hàm. Trong bài viết này, ta sẽ khám phá một số ví dụ về những ý tưởng này đang hoạt động, cũng như xác định một vài địa điểm mà chúng hiển thị có thể khiến bạn ngạc nhiên.Sau khi đọc cái này, bạn có thể :
- Xác định ứng dụng từng phần và cách nấu cà ri và giải thích sự khác biệt giữa hai phương pháp này.
- Sử dụng ứng dụng từng phần để sửa các đối số cho một hàm.
- Chức năng Curry để tạo điều kiện cho ứng dụng một phần
- Thiết kế các chức năng tạo điều kiện cho ứng dụng từng phần.
Ví dụ không có ứng dụng một phần
Như với nhiều mẫu, ứng dụng một phần dễ hiểu hơn với ngữ cảnh.
Hãy xem xét hàm buildUri
này:
function buildUri (scheme, domain, path) { return `${scheme}://${domain}/${path}` }
Ta gọi như thế này:
buildUri('https', 'twitter.com', 'favicon.ico')
Điều này tạo ra chuỗi https://twitter.com/favicon.ico
.
Đây là một chức năng hữu ích nếu bạn đang tạo nhiều URL. Tuy nhiên, nếu bạn làm việc chủ yếu trên web, bạn sẽ hiếm khi sử dụng scheme
ngoài http
hoặc https
:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico') const googleHome = buildUri('https', 'google.com', '')
Lưu ý sự tương đồng giữa hai dòng này: Cả hai đều truyền https
làm đối số ban đầu. Ta muốn loại bỏ sự lặp lại và viết một cái gì đó giống như:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Có một vài cách để làm điều này. Hãy xem làm thế nào để đạt được nó với ứng dụng một phần.
Ứng dụng một phần: Sửa đối số
Ta đã đồng ý rằng, thay vì:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
Ta muốn viết:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Về mặt lý thuyết, buildHttpsUri
thực hiện chính xác những điều tương tự như buildUri
, nhưng với một giá trị cố định cho mình scheme
tranh luận.
Ta có thể triển khai buildHttpsUri
trực tiếp như vậy:
function buildHttpsUri (domain, path) { return `https://${domain}/${path}` }
Điều này sẽ làm những gì ta muốn, nhưng vẫn chưa giải quyết được vấn đề của ta hoàn toàn. Ta đang sao chép buildUri
, nhưng mã hóa cứng https
làm đối số scheme
của nó.
Ứng dụng một phần cho phép ta làm điều này, nhưng bằng cách tận dụng mã ta đã có trong buildUri
. Đầu tiên, ta sẽ xem cách thực hiện việc này bằng cách sử dụng một thư viện tiện ích chức năng có tên là Ramda . Sau đó, ta sẽ thử làm điều đó bằng tay.
Sử dụng Ramda
Sử dụng Ramda, một phần ứng dụng trông giống như sau:
// Assuming we're in a node environment const R = require('ramda') // R.partial returns a new function (!) const buildHttpsUri = R.partial(buildUri, ['https'])
Sau đó, ta có thể làm:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Hãy phân tích những gì đã xảy ra ở đây:
- Ta đã gọi hàm
partial
của Ramda và truyền hai đối số: Thứ nhất, một hàm, được gọi làbuildUri
và thứ hai, một mảng chứa một giá trị"https"
. - Sau đó, Ramda trả về một hàm mới, hoạt động giống như
buildUri
, nhưng với"https"
là đối số đầu tiên của nó.
Việc chuyển nhiều giá trị hơn trong mảng sẽ sửa các đối số khác:
// Bind `https` as first arg to `buildUri`, and `twitter.com` as second const twitterPath = R.partial(buildUri, ['https', 'twitter.com']) // Outputs: `https://twitter.com/favicon.ico` const twitterFavicon = twitterPath('favicon.ico')
Điều này cho phép ta sử dụng lại mã chung mà ta đã viết ở nơi khác bằng cách cấu hình nó cho các trường hợp đặc biệt.
Ứng dụng một phần thủ công
Trong thực tế, bạn sẽ sử dụng các tiện ích như partial
khi nào bạn cần sử dụng ứng dụng một phần. Tuy nhiên, để minh họa, ta hãy cố gắng tự làm điều này.
Hãy xem đoạn mã trước, sau đó mổ xẻ.
// Line 0 function fixUriScheme (scheme) { console.log(scheme) return function buildUriWithProvidedScheme (domain, path) { return buildUri(scheme, domain, path) } } // Line 1 const buildHttpsUri = fixUriScheme('https') // Outputs: `https://twitter.com/favicon.ico` const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Hãy chia nhỏ những gì đã xảy ra.
- Trên Dòng 0, ta xác định một hàm có tên là
fixUriScheme
. Hàm này chấp nhận mộtscheme
và trả về một hàm khác. - Trên Dòng 1, ta lưu kết quả của việc gọi
fixUriScheme('https')
vào một biến có tên làbuildHttpsUri
, hoạt động giống hệt như version ta đã tạo bằng Ramda.
Hàm fixUriScheme
của ta chấp nhận một giá trị và trả về một hàm. Nhớ lại rằng điều này làm cho nó trở thành một hàm bậc cao hơn , hoặc HOF. Hàm trả về này chỉ chấp nhận hai đối số: domain
và path
.
Lưu ý , khi ta gọi hàm trả về này, ta chỉ truyền domain
và path
một cách rõ ràng, nhưng nó nhớ scheme
mà ta đã truyền trên Dòng 1. Điều này là do hàm bên trong, buildUriWithProvidedScheme
, có quyền truy cập vào tất cả các giá trị trong phạm vi hàm mẹ của nó , ngay cả sau khi hàm cha đã trả về. Đây là những gì ta gọi là đóng cửa .
Điều này khái quát. Bất kỳ khi nào một hàm trả về một hàm khác, hàm được trả về có quyền truy cập vào bất kỳ biến nào được khởi tạo trong phạm vi của hàm mẹ. Đây là một ví dụ điển hình về việc sử dụng bao đóng để đóng gói trạng thái.
Ta có thể làm điều gì đó tương tự bằng cách sử dụng một đối tượng với các phương thức:
class UriBuilder { constructor (scheme) { this.scheme = scheme } buildUri (domain, path) { return `${this.scheme}://${domain}/${path}` } } const httpsUriBuilder = new UriBuilder('https') const twitterFavicon = httpsUriBuilder.buildUri('twitter.com', 'favicon.ico')
Trong ví dụ này, ta cấu hình từng cá thể của lớp UriBuilder
với một scheme
cụ thể. Sau đó, ta có thể gọi phương thức buildUri
, phương thức này kết hợp domain
và path
mong muốn của user với scheme
cấu hình trước của ta để tạo ra URL mong muốn.
Khái quát hóa
Nhớ lại ví dụ mà ta đã bắt đầu với:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico') const googleHome = buildUri('https', 'google.com', '')
Hãy thực hiện một thay đổi nhỏ:
const twitterHome = buildUri('https', 'twitter.com', '') const googleHome = buildUri('https', 'google.com', '')
Lần này có hai điểm chung: Lược đồ, "https"
, trong cả hai trường hợp và đường dẫn, ở đây là chuỗi trống.
Hàm partial
mà ta đã thấy trước đó áp dụng một phần từ bên trái. Ramda cũng cung cấp partialRight , cho phép ta một phần áp dụng từ phải sang trái.
const buildHomeUrl = R.partialRight(buildUri, ['']) const twitterHome = buildHomeUrl('https', 'twitter.com') const googleHome = buildHomeUrl('https', 'google.com')
Ta có thể làm điều này xa hơn:
const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https']) const twitterHome = buildHttpsHomeUrl('twitter.com') const googleHome = buildHttpsHomeUrl('google.com')
Cân nhắc thiết kế
Để sửa cả đối số scheme
và path
cho buildUrl
, trước tiên ta phải sử dụng partialRight
, sau đó sử dụng partial
trên kết quả.
Điều này không lý tưởng. Sẽ tốt hơn nếu ta có thể sử dụng partial
(hoặc partialRight
), thay vì cả hai theo thứ tự.
Hãy xem nếu ta có thể sửa chữa điều này. Nếu ta xác định lại buildUrl
:
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}` }
Phiên bản mới này vượt qua các giá trị mà ta có thể biết trước. Đối số cuối cùng, domain
, là đối số mà ta có nhiều khả năng muốn thay đổi. Sắp xếp các đối số theo thứ tự này là một nguyên tắc chung.
Ta cũng có thể chỉ sử dụng partial
:
const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])
Điều này dẫn đến quan điểm rằng thứ tự đối số quan trọng. Một số đơn đặt hàng thuận tiện hơn cho việc áp dụng một phần so với những đơn đặt hàng khác. Hãy dành thời gian suy nghĩ về thứ tự đối số nếu bạn định sử dụng các hàm của bạn với ứng dụng một phần.
Ứng dụng một phần cà ri và tiện lợi
Bây giờ ta đã xác định lại buildUrl
với một thứ tự đối số khác:
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}` }
Lưu ý :
- Các đối số mà ta có nhiều khả năng muốn sửa nhất xuất hiện ở bên trái. Một trong những ta muốn thay đổi là tất cả các cách ở bên phải.
-
buildUri
là một hàm gồm ba đối số. Nói cách khác, ta cần vượt qua ba điều để nó chạy.
Có một chiến lược ta có thể sử dụng để tận dụng lợi thế này:
const curriedBuildUrl = R.curry(buildUrl) // We can fix the first argument... const buildHttpsUrl = curriedBuildUrl('https') const twitterFavicon = buildHttpsUrl('twitter.com', 'favicon.ico') // ...Or fix both the first and second arguments... const buildHomeHttpsUrl = curriedBuildUrl('https', '') const twitterHome = buildHomeHttpsUrl('twitter.com') // ...Or, pass everything all at once, if we have it const httpTwitterFavicon = curriedBuildUrl('http', 'favicon.ico', 'twitter.com')
Các món cà ri chức năng phải mất một chức năng, món cà ri nó, và trả về một chức năng mới, không giống như partial
.
Currying là quá trình chuyển đổi một hàm mà ta gọi cùng một lúc với nhiều biến, như buildUrl
, thành một chuỗi các lệnh gọi hàm, trong đó ta chuyển từng biến một.
-
curry
không sửa chữa các đối số ngay lập tức. Hàm trả về nhận nhiều đối số như hàm root . - Nếu bạn chuyển tất cả các đối số cần thiết cho hàm curried, nó sẽ hoạt động giống như
buildUri
. - Nếu bạn truyền ít đối số hơn so với hàm ban đầu đã nhận, hàm curried sẽ tự động trả về cùng thứ mà bạn nhận được bằng cách gọi
partial
.
Currying mang đến cho ta những gì tốt nhất của cả hai thế giới: Ứng dụng tự động từng phần và khả năng sử dụng các chức năng ban đầu của ta .
Lưu ý currying giúp tạo các version ứng dụng một phần của các hàm của ta dễ dàng hơn. Điều này là do các hàm curry thuận tiện để áp dụng một phần, miễn là ta đã cẩn thận về thứ tự đối số của bạn .
Ta có thể gọi curriedbuildUrl
giống như cách ta gọi buildUri
:
const curriedBuildUrl = R.curry(buildUrl) // Outputs: `https://twitter.com/favicon.ico` curriedBuildUrl('https', 'favicon.ico', 'twitter.com')
Ta cũng có thể gọi nó như thế này:
curriedBuildUrl('https')('favicon.ico')('twitter.com')
Lưu ý curriedBuildUrl('https')
trả về một hàm, hoạt động giống như buildUrl
, nhưng với schemas của nó được cố định thành "https"
.
Sau đó, ta ngay lập tức gọi hàm này bằng "favicon.ico"
. Điều này trả về một hàm khác, hoạt động giống như buildUrl
, nhưng với schemas của nó được cố định thành "https"
và đường dẫn của nó được cố định thành chuỗi trống.
Cuối cùng, ta gọi hàm này với "twitter.com"
. Vì đây là đối số cuối cùng nên hàm phân giải thành giá trị cuối cùng là: http://twitter.com/favicon.ico
.
Bài học quan trọng là: curriedBuldUrl
có thể được gọi như một chuỗi các lệnh gọi hàm, trong đó ta chỉ chuyển một đối số cho mỗi lệnh gọi. Đó là quá trình chuyển đổi một hàm của nhiều biến được truyền “tất cả cùng một lúc” thành một chuỗi “lệnh gọi một đối số” mà ta gọi là currying.
Kết luận
Hãy tóm tắt lại những điều chính:
- Ứng dụng một phần cho phép ta sửa các đối số của một hàm. Điều này cho phép ta lấy các chức năng mới, với hành vi cụ thể, từ các chức năng khác, tổng quát hơn.
- Currying chuyển đổi một hàm chấp nhận nhiều đối số “tất cả cùng một lúc” thành một chuỗi các lệnh gọi hàm, mỗi lệnh chỉ bao gồm một đối số tại một thời điểm. Các hàm có cấu trúc với thứ tự đối số được thiết kế tốt sẽ thuận tiện để áp dụng một phần.
- Ramda cung cấp các tiện ích
partial
,partial
partialRight
vàcurry
. Các thư viện phổ biến tương tự bao gồm Underscore và Lodash .
Các tin liên quan
Cách gói một gói JavaScript Vanilla để sử dụng trong React2019-12-12
Cách phát triển một trình tải lên tệp tương tác với JavaScript và Canvas
2019-12-12
Cách sử dụng map (), filter () và Reduce () trong JavaScript
2019-12-12
Giới thiệu về Closures và Currying trong JavaScript
2019-12-12
Bắt đầu với các hàm mũi tên ES6 trong JavaScript
2019-12-12
Cách đếm số nguyên âm trong một chuỗi văn bản bằng thuật toán JavaScript
2019-12-12
Cách sử dụng phép gán cấu trúc hủy trong JavaScript
2019-12-12
Giải thích về lập trình chức năng JavaScript: Kết hợp & truyền tải
2019-12-12
Cách sử dụng .every () và .some () để điều khiển mảng JavaScript
2019-12-12
Chuyển đổi Mảng sang Chuỗi trong JavaScript
2019-12-05