Functional Aspects

Tram Ho

Song song với Procedural Programming, và ở cùng cấp độ quan sát về việc kiến trúc phần mềm từ các sub-program đó là mô hình Lập Trình Hàm Functional Programming. Ở thời điểm đó thì chúng ta đã điểm qua và so sánh song song một số nét đặc trưng của ProceduralFunctional.

Tuy nhiên trong khuôn khổ của một bài giới thiệu ngắn thì vẫn còn rất nhiều những điểm chi tiết mà mình không thể mang vào để giới thiệu về các mô hình lập trình này. Và cụ thể là sau khi đi qua Sub-Series Ada thì điều đó lại càng rõ ràng, những công cụ lập trình và các khái niệm liên quan, được cung cấp bởi một ngôn ngữ được thiết kế trọng tâm hướng đến Procedural có nhiều phần không thể liệt kê được trong một bài viết.

Và bây giờ đã là thời điểm phù hợp để chúng ta tìm hiểu chi tiết về các công cụ và các khái niệm liên quan mà một ngôn ngữ được thiết kế trọng tâm Functional cung cấp. Thực tế thì để tìm hiểu về Functional Programming một cách nghiêm túc và sử dụng những lợi điểm của Functional thì môi trường thuận lợi nhất hiện tại đang là ngôn ngữ Haskell. Tuy nhiên để duy trì lượng cú pháp phải ghi nhớ tạm ở mức độ phù hợp thì mình đã chọn phương thức học tham chiếu từ Haskell và triển khai trong Elm với hết khả năng mà Elm đang có.

Mặc dù cũng là một ngôn ngữ thuần Functional nhưng so với Haskell thì Elm hỗ trợ ít cú pháp biểu thị code hơn và chỉ kèm theo vừa đủ các công cụ mang tính kiến trúc căn bản. Một số khái niệm về các công cụ tạo dạng thức triển khai code Functional được Haskell hỗ trợ sẵn như Type Class, Functor, Monad, v.v… khi muốn sử dụng trong Elm thì chúng ta sẽ phải tự định nghĩa mô phỏng lại. Tuy nhiên điều đó cũng sẽ đem lại điều kiện để chúng ta hiểu rõ hơn về các công cụ này và nắm bắt tốt hơn tư duy lập trình hàm.

Functional Aspects

Trong Sub-Series trước của Elm, chúng ta đã nói về Declarative Programming – một trong hai mô thức căn bản để viết một đoạn code mô tả logic cần máy tính thực hiện. Tuy nhiên Declarative cũng giống như Imperative, chỉ nói về việc biểu thị logic ở cấp độ chi tiết chứ không phải là ở cấp độ kiến trúc chương trình. Và sau đó, khi bắt đầu quan tâm tới việc thiết kế và tổ chức các sub-program thì chúng ta mới có ProceduralFunctional.

Điều đó có nghĩa là một ngôn ngữ Procedural cũng có thể được thiết kế với cú pháp sử dụng ở dạng Declarative, ví dụ như SQL; Và một ngôn ngữ Functional cũng có thể được thiết kế với cú pháp sử dụng ở dạng Imperative, ví dụ như F-sharp. Tuy nhiên sự thực thì đúng là các ngôn ngữ thuần Functional đều sử dụng cú pháp Declarative, và tuyệt đại đa số các ngôn ngữ Procedural đều sử dụng cú pháp Imperative.

Trong Sub-Series này thì chúng ta sẽ chỉ đề cập tới khía cạnh Functional được biểu thị trong Elm để tìm hiểu và có khả năng triển khai trong cả những ngôn ngữ Imperative như JavaScript nếu muốn.

first-class function  &  function composition
higher-order function  &  currying function
type variable  &  type class
functor  &  applicative
monad  &  monoid

Trên đây là danh sách liệt kê một số nét đặc trưng và khái niệm liên quan tới Functional Programming mà chúng ta sẽ tìm hiểu trong Sub-Series này với sự tham chiếu từ Haskell. Và trong bài viết mở đầu này thì chúng ta sẽ nói về các khái niệm First-Class FunctionFunction Composition.

First-Class Function

Đây là điểm đặc trưng căn bản nhất và quan trọng nhất để bất kỳ ngôn ngữ nào có thể được xem là có hỗ trợ Functional Programming. Khái niệm First-Class Function là cách nói ngắn gọn của câu Function is First-Class Citizen – có thể hiểu nôm na là các Hàm Function được xếp vào lớp các thành tố quan trọng nhất của phần mềm, tương đương với Kiểu Dữ Liệu Type. Và biểu thị cần được hỗ trợ đó là một Hàm có thể được xem là một giá trị Value, có thể được gán vào các biến, có thể được sử dụng làm các toán hạng của một biểu thức giữa các Hàm, có thể được truyền vào lời gọi Hàm khác, và có thể là kết quả trả về của một lời gọi Hàm nào đó.

Có thể gán một Hàm vào một biến để lưu trữ và sử dụng sau đó.

Chính vì lý do này nên mặc dù bạn có thể không chạm tới các ngôn ngữ thuần Functional nhưng khi dạo quanh một số ngôn ngữ lập trình phổ thông để tham khảo tính năng và cú pháp, thì khả năng rất lớn là bạn sẽ gặp nhiều trường hợp định nghĩa hàm trong một số ngôn ngữ có hỗ trợ viết ở dạng biểu thức với phép gán = vào một tên định danh nào đó. Kiểu cú pháp này chính là thứ xuất phát từ Functional Programming.

module Main exposing (main)
import Html exposing (Html, text)

main : Html message
main = text (greet "Elm")

greet : String -> String
greet name = "Hello " ++ name ++ " again !"

Và trong JavaScript như chúng ta đã biết từ Series lập trình web đầu tiên:

// -- greet : String -> String
const greet = (name) => "Hello" + name + "again !"

Hm… vị trí của phép gán = dường như có phần hơi khác nhau. Trong code ví dụ của Elm thì chúng ta đang có dạng mô tả hàm toán học f(x) = ... x ..., và trong đó thì vị trí của f chính là greet và vị trí của xname; Còn trong code ví dụ của JS thì tính từ vị trí (name) => ... có ý nghĩa tương đương với định nghĩa trong Elm và tạo ra một hàm f vô danh để gán vào hằng greet.

Tuy nhiên chúng ta cũng có thể sửa lại code main của Elm để biểu thị rõ ràng hơn rằng – Hàm Function cũng là một Giá Trị Value như các kiểu dữ liệu khác. Hãy thử gán hàm greet vào một biến trong hàm main, sau đó gọi hàm thông qua tên biến này.

-- module ...

main : Html message
main =
   let make = greet
   in text (make "Elm")

-- greet : ...

Function Composition

Khái niệm Kết Hợp Hàm Function Composition xuất hiện đồng thời với First-Class Function, bởi mục đích của việc nhìn nhận một Hàm Function là một Giá Trị Value và yêu cầu thiết kế ngôn ngữ hỗ trợ việc lưu trữ Hàm vào biến là để chúng ta có thể thực hiện các phép toán trên các hàm và sau đó thu được một Hàm tổng bộ all-in-one và lưu vào một biến. Sau đó áp dụng hàm này trên dữ liệu đầu vào input và thu về kết quả output.

Ý tưởng này cũng được vay mượn từ Toán Học. Khi chúng ta có biểu thức h(x) = f(g(x)) thì chúng ta cũng có thể viết h = f . g để biểu thị rằng hàm h là kết quả thu được khi kết hợp hàm f và hàm g.

main : Html message
main =
   let make = text << greet
   in make "Elm"

Ở đây chúng ta có code Hello, Elm ! được viết lại với main được định nghĩa là kết quả thu được sau khi gọi Hàm make với chuỗi "Elm", và hàm make lúc này được định nghĩa là hàm kết hợp được tạo ra từ hàm text và hàm greet. Dạng thức viết code này sẽ được sử dụng rất nhiều trong các Hàm chủ đạo điều khiển logic hoạt động chính của chương trình, tương đương với vai trò của các Thủ Tục trong môi trường Procedural; Bởi như chúng ta đã biết thì trong môi trường Procedural, các function chỉ đóng vai trò sub-program hỗ trợ cho các procedure chủ đạo điều khiển logic hoạt động chính ngay dưới cấp của main.

Về việc phân chia vai trò của các Hàm chủ đạo và các Hàm tiện ích hỗ trợ các thao tác tính toán thì trong môi trường Functional không có quy ước nào. Tất cả hoàn toàn là lựa chọn của người viết code khi thiết kế các Hàm và phải thực hiện ghi chú hay đưa ra quy ước đặt tên convention để tự sắp xếp. Tuy nhiên đặc điểm chung của các ngôn ngữ Functional là rất cẩn thận trong việc tách rời code tạo hiệu ứng biên side-effect tác động tới môi trường bên ngoài khỏi code xử lý logic chính, vì vậy nên chúng ta cũng sẽ sớm học các dạng thức triển khai pattern được áp dụng cho việc này.

Một lưu ý nhỏ về phép thực thi thao tác kết hợp hàm << mà chúng ta vừa sửa dụng, đó là trong trường hợp muốn viết liệt kê các hàm theo chiều ngược lại theo lối tư duy Imperative thì chúng ta có thể sử dụng ký hiệu >>.

main : Html message
main =
   let make = greet >> text
   in make "Elm"

Trong một bài viết trước đó của Sub-Series Declartive thì chúng ta đã sử dụng phép thực thi <| hoặc |> để truyền kết quả của một lời gọi hàm vào một hàm khác ở phía bên trái hoặc phía bên phải. Tuy nhiên đó không phải là Function Composition mà vẫn là các lời gọi hàm xếp chồng và được điều chỉnh mức ưu tiên gọi hàm nhờ các phép thực thi thay thế cho các cặp ngoặc đơn ().

Mục đích của các phép thực thi kết hợp hàm <<>> là để cung cấp thêm một phương thức khác cho việc cấu trúc chương trình. Rất có thể nếu bạn xây dựng một ứng dụng đủ phức tạp thì trong chương trình của bạn ngoài các dòng dữ liệu data-flow sẽ còn xuất hiện thêm sự lưu chuyển các hàm qua các điểm thực hiện kết hợp hàm.

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo