Lập trình hàm (tiếng Anh Functional Programming) là phương pháp lập trình quy việc tính toán về đánh giá các hàm, do vậy không sử dụng khái niệm trạng thái và biến số (có thể thay đổi giá trị trong quá trình tính toán). Việc tính toán được thực hiện bằng cách viết lại các biểu thức mà không bằng cách thay đổi trạng thái của chương trình. Đặc điểm của ngôn ngữ lập trình hàm thuần túy là không sử dụng khái niệm bộ nhớ.
Các thành phần cơ bản[sửa]
Từ cách nhìn ngữ nghĩa, chương trình bao gồm một loạt thao tác xác định giá trị của các biểu thức, có thể phức tạp tùy ý và tạo một liên kết mới vào môi trường. Hàm bậc cao và hàm đệ quy là hai khái niệm cơ bản trong lập trình hàm. Sử dụng hàm bậc cao và hàm đệ quy làm cho cách thức xác định giá trị trở nên linh hoạt và hiệu quả. Ngôn ngữ lập trình hàm không sử dụng biến số có thể thay đổi giá trị trong quá trình xử lý, tính toán, nên cũng không cần dùng phép gán như trong các ngôn ngữ lập trình khác. Việc tính toán được thể hiện bằng việc thay đổi môi trường, về cơ bản thông qua các thao tác hàm. Trong lập trình hàm thuần túy, giá trị xuất ra của hàm chỉ phụ thuộc vào các tham số đầu vào của hàm, vì thế gọi hàm f hai lần với cùng giá trị tham số x sẽ cho ra cùng kết quả f (x). Điều này làm cho chương trình dễ hiểu hơn rất nhiều so với lập trình thủ tục.
Hàm bậc cao và hàm thuần túy là hai loại hàm cơ bản trong lập trình hàm:
Hàm bậc cao là hàm có thể nhận các hàm khác làm tham số hoặc có thể trả ra kết quả là một hàm số. Ví dụ hàm vi phân là một hàm bậc cao, nếu f (x)= x2 thì df/dx = 2x = g (x). Hàm bậc cao cho phép áp dụng từng phần, nghĩa là hàm lần lượt sử dụng từng tham số của nó, mỗi lần trả về hàm mới và nhận vào tham số tiếp theo. Ví dụ toán tử cộng sẽ cộng lần lượt từng số tự nhiên lại.
Hàm thuần túy là hàm không có hiệu ứng phụ (không có bộ nhớ trung gian khác). Điều này dẫn tới hàm thuần túy có các đặc điểm hữu ích cho phép tối ưu mã nguồn:
- Khi kết quả của biểu thức thuần túy không được sử dụng, có thể xóa đi và không ảnh hưởng đến các biểu thức khác.
- Với cùng bộ giá trị của các tham số, kết quả đánh giá hàm luôn cho 1 giá trị duy nhất dù có gọi hàm bao nhiêu lần ở bất cứ thời điểm nào.
Khi không có sự phụ thuộc dữ liệu giữa hai hàm thuần túy, thứ tự thực hiện của chúng có thể tùy ý, có thể thực hiện song song và không ảnh hưởng đến kết quả của nhau. Với các ngôn ngữ lập trình hàm không phải thuần túy, thường sẽ có thêm từ khóa giúp người lập trình đánh dấu các hàm thuần túy, nhờ vậy trình biên dịch sẽ tối ưu hiệu quả mã nguồn.
Vòng lặp trong ngôn ngữ lập trình hàm được thực hiện thông qua đệ quy. Hàm đệ quy sẽ gọi chính nó để thực hiện lặp đi lặp lại một thao tác nào đó. Ví dụ hàm đệ quy factorial tính n! được viết trong ngôn ngữ Scheme như sau: (define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1))))))
Trong ví dụ trên factorial (n) trả ra kết quả 1 nếu n = 0, ngược lại kết quả trả ra là tích của n và kết quả của hàm factorial (n-1). Các mẫu đệ quy phổ biến đều có thể khử được bằng hàm bậc cao. Các hàm bậc cao đóng vai trò tương tự các cấu trúc điều khiển thực hiện lặp trong ngôn ngữ lập trình thủ tục.
Có thể chia ngôn ngữ lập trình hàm thành 2 loại tùy thuộc vào cách tính toán biểu thức: tính toán chặt và tính toán không chặt. Tính toán chặt luôn luôn tính toán tất cả các tham số của hàm trước khi xử lý hàm. Tính toán không chặt không tính toán các tham số của hàm trừ khi nó cần giá trị tham số đó để tính toán hàm. Ví dụ, xét biểu thức length ([2+1, 3*2, 1/0, 5-4]) (ở đây length (a) là hàm trả về số phần tử của mảng a) sẽ không tính được theo phép toán chặt vì phép chia cho 0 ở phần tử thứ 3 không tồn tại, nhưng với tính toán không chặt thì kết quả là 4 do phép chia 0 không được đánh giá mà chỉ quan tâm đến số phần tử của mảng. Cách tính toán không chặt được dùng mặc định trong một số ngôn ngữ lập trình hàm thuần thúy như Miranda, Clean và Haskell.
Lập trình hàm trong ngôn ngữ phi hàm[sửa]
Các ngôn ngữ lập trình hàm phát triển từ giải tích lambda, hệ thống tính toán dựa trên khái niệm hàm. Trước đây, lập trình hàm thường được dùng trong giới học thuật, ít phổ biến hơn lập trình thủ tục, nhưng ngày nay nhiều ngôn ngữ lập trình hàm được sử dụng trong công nghiệp và giáo dục như Common Lisp, Scheme, Erlang, Ocaml, Haskell, F#,... Lập trình hàm đóng vai trò quan trọng trong thành công của các ngôn ngữ chuyên biệt như R trong thống kê, J, Q, K trong phân tích tài chính, Xquery/XSLT cho XML. Các ngôn ngữ như SQL và Lex/Yacc cũng sử dụng khái niệm của lập trình hàm, ví dụ: không cho phép đối tượng thay đổi giá trị hoặc định nghĩa các hàm thuần túy.
Cách thức lập trình hàm cũng được sử dụng trong các ngôn ngữ lập trình không thuộc nhóm lập trình hàm (ngôn ngữ lập trình phi hàm) như C++11, Python, Scala, Kotlin, PHP, Perl. Các ngôn ngữ này sử dụng các khái niệm của lập trình hàm như: hàm thuần túy, hàm bậc cao. Ví dụ trong ngôn ngữ C++11 để chỉ ra cho trình biên dịch hàm thuần túy, cần thêm gnu:: pure trước định nghĩa hàm. Điều này giúp trình biên dịch có thông tin để tối ưu mã nguồn.
gnu:: pure int square (int x);
Hàm bậc cao có thể được sử dụng trong ngôn ngữ C++11 như ví dụ sau:
int resultOfCalculation (const std:: function<int (int, int)>& op, int x, int y) {
return op (x, y); }
Các ngôn ngữ lập trình hàm được phát triển từ những năm 1930 bởi Alonzo Church. LISP là ngôn ngữ lập trình hàm đầu tiên được phát triển cuối những năm 1950 bởi John McCarthy, Viện công nghệ Massachusetts (MIT), cho các dòng IBM 700/7000. Ban đầu LISP là ngôn ngữ đa mô hình. Hàm của LISP được được định nghĩa, sử dụng ký hiệu lambda của Church. Scheme, Clojure, Common LISP là các thế hệ sau của LISP.
Ngôn ngữ IPL (Information Processing Language) được đề xuất năm 1956, là hợp ngữ cho phép thao tác trên danh sách các ký hiệu.
Đầu những năm 1960, Kenneth E. Iverson phát triển ngôn ngữ APL. Sau đó đến khoảng những năm đầu 1990 Iverson và Roger Hui tạo ra ngôn ngữ J. Khoảng giữa những năm 1990 ngôn ngữ K được phát triển bởi Arthur Whitney, người từng là cộng sự của Iversion. Ngôn ngữ K cùng với thế hệ sau Q được thương mại hóa trong lĩnh vực tài chính. Ngôn ngữ ML ra đời năm 1973 bởi Robin Milner ở đại học Edinburgh. Từ ngôn ngữ ML, ra đời các ngôn ngữ Hope, Ocaml, Standard ML.
Ngôn ngữ lập trình hàm Miranda được phát triển bởi David Turner năm 1985. Ngôn ngữ này có ảnh hưởng mạnh đến ngôn ngữ Haskell (1990). Miranda là độc quyền, trong khi đó với sự đồng thuận Haskell đã tạo ra một chuẩn mở cho lập trình hàm.
Tài liệu tham khảo[sửa]
- Mira Balaban. Principles of Programming Languages. Lecture notes. Ben-Gurion University of the Negev, Faculty of Natural Science, Department of Computer Science, 2017.
- Gilles Dowek. Principles of Programming Languages. Springer. DOI: 10.1007/978-1-84882-032-6.2009.
- Hudak, Paul, "Conception, evolution, and application of functional programming languages" (PDF). ACM Computing Surveys. 21 (3), 1989: 359–411. doi:10.1145/72551.72554.