- vừa được xem lúc

[Functional Programming + Elm] Bài 2 - Higher-Order Function & Currying Function

0 0 18

Người đăng: Semi Dev

Theo Viblo Asia

Hãy khoan chưa vội nói tới những khái niệm mới mà chúng ta đã dự định trong bài viết này. Phần mở đầu ở đây chúng ta sẽ nói tới một vài yếu tố liên quan tới các khái niệm ở bài trước. Đầu tiên chúng ta sẽ nói tới các biến được tạo ra trong các cú pháp gắn kèm binding như let .. in.

Mặc dù các ngôn ngữ Functional đều được thiết kế với đặc trưng định kiểu dữ liệu chặt chẽ. Tuy nhiên các biến được tạo ra sẽ đều được ngầm định kiểu dữ liệu và trình biên dịch sẽ tự động tìm được thông tin từ giá trị được gán vào mỗi biến. Và sau đó để đảm bảo tính nhất quán và sự chặt chẽ trong logic code, các Biến trong môi trường thiết kế đặc trưng cho Functional Programming mặc định đều sẽ không thay đổi.

Điều này có nghĩa là các Biến trong các môi trường Functional như Elm, Haskell, PureScript, có chức năng tương đương với các Hằng constant trong các môi trường lập trình khác. Ngay cả ở thao tác định nghĩa các hàm thì các yếu tố ở phía bên trái ký hiệu =, bao gồm biến đặt tên hàm và các tham số đầu vào cũng đều là các yếu tố bất dịch immutable. Khái niệm bất biến immutable và hiệu ứng biên side-effect được sử dụng rất nhiều trong môi trường Functional và chúng ta sẽ lưu ý từ đây.

elm repl
---- Elm 0.19.1 ----------------------------------------------------------------
Say :help for help and :exit to exit! More at <https://elm-lang.org/0.19.1/repl>
--------------------------------------------------------------------------------
> _

Higher-Order Function

Higher-Order Function được hiểu nôm na là các Hàm có vị trí quan sát cao hơn.

Ví dụ như khi chúng ta truyền một Hàm g vào lời gọi Hàm f và sau đó logic hoạt động bên trong Hàm f sẽ thực hiện việc gọi Hàm g để ủy thác một công việc nào đó. Lúc này Hàm f được xem là Higher-Order Function so với Hàm g; Bởi vì f đã biết thông tin về g thông qua định kiểu tham số, còn g thì không biết thông tin gì về f.

> List.map not [True, False]
> [False,True] : List Bool

Trong ví dụ trên thì chúng ta đã truyền hàm not vào lời gọi hàm map của module List, và ủy thác việc nghịch đảo các giá trị Bool bên trong List được truyền vào ở vị trí tham số tiếp theo. Như vậy List.map ít nhất đã biết được rằng hàm được truyền vào vị trí ở tham số đầu tiên có dạng (a -> b) để chuyển đổi giá trị của một phần tử trong List. Và logic hoạt động của List.map là thực hiện việc gọi hàm not với lần lượt từng phần tử của List để thu được các giá trị mới và tạo về một List kết quả hoàn toàn mới.

Chúng ta cũng có thể sử dụng các biểu thức Hàm Vô Danh lambda có cú pháp khá tương đồng với JavaScript để truyền vào List.map. Trong trường hợp này, Elm sẽ ngầm định kiểu dữ liệu của tham số đầu vào cho Hàm Vô Danh là kiểu của dữ liệu trong List, còn kết quả trả về thì sẽ tùy thuộc vào logic bên trong lambda.

> List.map (\n -> n * 9) (List.range 0 9)
[0,9,18,27,36,45,54,63,72,81] : List Int > (\n -> n * 9)
<function> : number -> number

Và đây là cách mà JavaScript đã triển khai sẵn HOD hỗ trợ cho người viết code sử dụng với phương thức map của các mảng Array.

var origin = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
var nile = origin.map ((n) => n * 9) console.log (nile)

Khái niệm Higher-Order Function cũng có thể được biểu thị trong trường hợp khác, khi Hàm f trả về một giá trị là một Hàm g. Lúc này f cũng được xem là Hàm có góc quan sát cao hơn so với g. Tuy nhiên để làm ví dụ mô tả thì chúng ta sẽ chuyển sang khái niệm liên quan tiếp theo là Currying Function.

Currying Function

Trong cửa sổ REPL của Elm, chúng ta hãy thử kiểm tra xem thông tin định kiểu của List.map, bởi chúng ta đã biết tới Higher-Order Function và biết đâu sẽ có lúc muốn tự định nghĩa một Hàm HOD như vậy.

> List.map
<function> : (a -> b) -> List a -> List b

Như vậy là chúng ta có List.map là một hàm <function>, sẽ nhận vào tham số đầu tiên là một hàm (a -> b), và tham số tiếp theo là một danh sách List a, và trả về kết quả là một danh sách mới List b. Tuy nhiên chúng ta cũng có thể đọc thông tin định kiểu của List.map với tham số sau và kết quả trả về được nhóm lại bằng ngoặc đơn () như thế này:

<function> : (a -> b) -> (List a -> List b)

Đó có nghĩa là List.map là một hàm <function>, sẽ nhận vào tham số là một hàm (a -> b) và trả về một hàm mới (List a -> List b). Như vậy chúng ta có thể hiểu List.map còn là Higher-Order Function của hàm (List a -> List b) nữa.

Lúc này đứng từ góc độ sử dụng hàm List.map, chúng ta sẽ có thể tạo ra hàm mới (List a -> List b) rồi sau đó mới sử dụng hàm này cho các List khác nhau.

> kyudo = List.map (\n -> n * 9)
<function> : List number -> List number > kyudo (List.range 0 9)
[0,9,18,27,36,45,54,63,72,81] : List Int > kyudo [0,10,20,30,40,50,60,70,80,90]
[0,90,180,270,360,450,540,630,720,810] : List number

Như vậy List.map đã được sử dụng bằng cách áp dụng các tham số từng phần partial application, thay vì các tham số được truyền vào cùng một lượt trong một lời gọi hàm. Và thao tác định nghĩa hàm với các tham số được xếp lớp như vậy để sau đó chúng ta có thể áp dụng từng phần được gọi là Currying Function.

Trong nhiều ngôn ngữ lập trình hàm bao gồm ElmHaskell, PureScript đã kể ở trên thì cú pháp định nghĩa hàm đã tự động hóa Currying. Ở các ngôn ngữ khác nếu không được hỗ trợ về mặt cú pháp thì chúng ta sẽ phải viết hơi dài dòng hơn ví dụ như JavaScript trước khi có cú pháp lambda . Còn đối với các ngôn ngữ có hỗ trợ cú pháp lambda thì chúng ta chỉ cần viết nối tiếp các lambda có tham số đơn.

// -- map : (a -> b) -> (Array a -> Array b)
function map (f) { return function ($array) { /* ... */ }
} // -- map : (a -> b) -> (Array a -> Array b)
const map => (f) => ($array) => { /* ... */ }

Tuy nhiên nếu tự định nghĩa một hàm Array.map như thế này trong JavaScript, hiển nhiên chúng ta sẽ cần phải sử dụng tới các yếu tố Imperative như tạo mảng kết quả rỗng và cập nhật qua các vòng lặp. Điểm quan trọng mà chúng ta cần lưu ý là mảng ban đầu $array không nên bị thay đổi về mặt nội dung sau bất kỳ thao tác nào.

(chưa đăng tải) [Functional Programming + Elm] Bài 3 - Type Variable & Type Class

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 51

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 31

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 27

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 47

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 266

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 35