Bài viết được sự cho phép của tác giả Lê Nhật Thanh
Trong thế giới kiếm hiệp Kim Dung, Trương Tam Phong là nhân vật có võ công cao nhất (theo Kim Dung). Bởi vì ông sở hữu bộ võ công Thái Cực Quyền và Thái Cực Kiếm. Tinh hoa võ công khác hẵn hoàn toàn với thế giới võ công đương đại. Lấy nhu khắc cương.
Thế giới lập trình cũng vậy. Lập trình hướng đối tượng (OOP) luôn được xem là tinh hoa của võ công.
Các bạn tiếp cận với OOP rất sớm, ngay lúc mới bắt đầu học lập trình.
Có rất nhiều người cho rằng OOP là mô hình để thiết kế phần mềm tốt nhất. Và mình xin nhắc lại, OOP là mô hình lấy đối tượng (object) làm gốc.
Mình ví OOP như là thế giới võ công đương đại trong Kim Dung. Vậy Thái Cực Quyền trong thế giới lập trình là gì?
Bạn đã bao giờ nghe về functional programming chưa? Lập trình theo phong cách lấy hàm (function) là trung tâm.
Bây giờ, hãy cùng đi vào thế giới hoàn toàn khác với những gì chúng ta thường biết với OOP.
#1 Thái cực bắt nguồn từ đâu
Ngày xửa ngày xưa, có hai cao thủ võ lâm. Alan Turing và Alonzo Church.
Alan Turing tinh thông môn võ Turing Machine. Còn Alonzo Church thì nổi danh với Lambda Calculus.
Cả hai đều là võ lâm cao thủ thời bấy giờ. Đã trãi qua nhiều trận đấu long trời lở đất. Nhưng đều không phân biệt thắng thua.
Sau đó, cả hai mới thống nhất với nhau một bản hiệp ước Church – Turing.
Khẩu quyết của Turing Machine chính là việc nắm giữ các trạng thái của tiến trình và state machine.
Còn linh hồn của Lambda Calculus tập trung cho việc xây dựng các tính chất của các hàm toán học.
Sau này, chính Turing Machine đã đặt nền móng cho sự ra đời của lập trình hướng đối tượng (OOP). Còn Lambda Calculus là nguồn gốc ra đời của Functional Programming (FP).
Lưu ý rằng, hai trường phái này chỉ là hai trong số nhiều trường phái trong võ lâm lúc bấy giờ. Nhưng đây là hai trường phái lớn nhất!
Lịch sử trôi qua, có vẻ OOP chiếm ưu thế hơn rất nhiều so với FP. Bạn thử nhìn xung quanh để kiểm chứng. Các buổi phỏng vấn, ngay trong trường đại học. Bạn tiếp xúc với OOP rất nhiều.
Nhưng ở một thế giới khác, FP vẫn luôn âm thầm phát triển từng ngày một. Hãy nhìn những Lisp, F#, Haskell, Clojure, Erlang, … là những ngôn ngữ đại diện cho trường phái FP.
Trong 1 thập kỉ trở lại đây. FP đang trở lại! Người ta bắt đầu phàn nàn về OOP.
Đã có những bài viết trình bày rất rõ về FP. Họ là những tín đồ, fan cuồng của FP. Ví dụ như bài viết “So You Want to be a Functional Programmer” của Charles Scalfani.Thậm chí, Scalfani còn đề cao Functional Programming như nấc thang tiến hóa trong lịch sử lập trình. Anh ấy còn có một bài viết khác gây tranh cãi rất nhiều! Goodbye, Object Oriented Programming.
Trên thực tế! FP hay OOP là hai trường phái tu luyện khác nhau. Phần nào cũng có cái lợi thế hay bất lợi riêng. Chúng ta không nên so sánh bên nào ngon hơn!
#2 Những khẩu quyết của Functional Programming
Trong phần này, chúng ta sẽ đi vào những thứ cơ bản nhất của FP. Bước đầu dấn thân vào thái cực!
Mình có một lưu ý: FP là một con đường dài. Nếu muốn giác ngộ, bạn phải kiên trì! Và trong bài viết này, mình không thể đi hết các khẩu quyết (concept) của FP. Vì đơn giản là không đủ!
Functional Programming là phương pháp lập trình lấy function làm đơn vị thao tác cơ bản.
Về lý tưởng, FP chỉ có function với function. Không có lệnh gán, không có vòng lặp, không có trạng thái toàn cục. Trong FP, chúng ta sẽ kết hợp các function lại với nhau và nhảy múa với chúng!
Phải nói rằng FP đang dần dần trở lại thế giới lập trình ở hiện đại. Bằng chứng là các bản cập nhật của các ngôn ngữ, framework mới. Đang dần hướng về FP (như Javascript, Python, Golang, …).Bạn hãy nhìn vào ReacJS, một framework với 70% FP và 30% OOP (có thể nói rằng như thế).
Các lập trình viên hiện đại thường kết hợp sự linh hoạt của OOP và FP lại với nhau. Mục đích cuối cùng là để có được một chương trình đúng, nhanh, gọn, đẹp.
Và để có thể bước vào thế giới Functional Programming. Bạn phải học một số khẩu quyết quan trọng sau!
Xem thêm nhiều việc làm ASP.NET Core hấp dẫn trên TopDev
Immutability hay tính bất biến
Khẩu quyết thứ nhất của functional programing là: Những gì đã được khai báo trước đó thì không được thay đổi nữa!
Nghĩa là những thứ đã được khai báo trước đó. Sẽ không bao giờ được thay đổi trong quá trình chạy chương trình!Bạn có thấy điều này lạ không? Khác hẵn với những gì ta đã học trong OOP. Những tư duy lập trình cũ. Nhưng nó chính là khẩu quyết đầu tiên của FP – tính bất biến.
Chúng ta cùng lấy một ví dụ về tính bất biến và không bất biến:
/* Đoạn code dưới đây không phải là bất biến vì giá trị biến bị thay đổi */ var immutabilityVar = "lenhatthanh.com"; immutabilityVar = "coderdocs.info"; /* Còn đoạn code dưới đây thoả mãn tính bất biến của FP */ var immutabilityVar = "lenhatthanh.com"; var immutabilityVar2 = "coderdocs.info";
Bất biến là khẩu quyết tối quan trọng trong FP. Nếu viết code theo phong cách FP, bạn cần hạn chế tối đa việc thay đổi giá trị biến hay object. Tốt nhất là không nên thay đổi, mọi thứ trong FP nên là const.
Khi ES6 release, đã cho ra 2 cách khai báo mới là const
và let
. Người ta khuyên rằng nên dùng const
và dùng let
chỉ khi nào cần thiết.
Bạn có nhớ tới vòng lặp for
, while
không? Những vòng lặp này hay dùng một biến index duyệt vòng lặp. Và vì tính bất biến, nên FP đã loại bỏ vòng lặp!
Vậy thì bạn sẽ hỏi là, chúng ta sẽ dùng gì để thay thế cho vòng lặp?
Đệ quy (recursive), cao hơn chúng ta dùng một số function khác. Tùy vào độ support của ngôn ngữ! Quay lại, đệ quy sẽ thay thế cho vòng lặp. Có nhiều bạn có lẽ không quen với tư duy của đệ quy. Nhưng nếu bạn đã bước vào FP, thì hãy luyện đệ quy.
/** * Bài toán ở đây là tính giai thừa của n (n!) * Chúng ta sẽ giải quyết bài toán theo vòng lặp và đệ quy **/ // Chúng ta viết theo vòng lặp cực kì đơn giản result = 1; for (i = 1; i <= n; i++) { result *= i; } // Còn nếu chúng ta viết với đệ quy thì sao? function factorialOfNumber (n) { if (n === 1) { return n; } return n * factorialOfNumber(n - 1); }
Vậy lợi ích của tính bất biến là gì?
Bạn cũng biết chúng ta hay gặp bug khi thay đổi giá trị của biến mà chúng ta không kiểm soát được. Hoặc các trạng thái bị thay đổi một cách bất ngờ. Vì bất biến, nên việc thay đổi là không xảy ra. Nên rủi ro gặp bug (do các biến gây ra) là nhỏ hơn và chương trình dễ debug hơn. Chúng ta dễ dàng kiểm soát được giá trị các biến. Và tránh được những thay đổi không mong muốn trong chương trình.
Purity
Purity là sự thật khiết, trinh trắng!
Khẩu quyết thứ 2 trong FP là.
Tất cả các function đều phải là Pure function. Nghĩa là những function này không được thay đổi bất cứ thứ gì bên ngoài nó. Không được thay đổi tham số đầu vào. Không có hiệu ứng phụ (side effect).
Chúng ta cùng lấy một ví dụ về function có hiệu ứng phụ.
function handleX (n) { const returnVal = n * n + n * 2 + 1; makeAjaxCall(returnVal); return returnVal; }
Rõ ràng trong function handleX
còn gọi thêm Ajax, và không biết sẽ xử lý gì trong đó. Nên đây được gọi là hiệu ứng phụ. Và function này không phải là Pure function.
Javascript hay những ngôn ngữ như khác thường có rất nhiều hiệu ứng phụ side effect. Rõ ràng là chương trình chúng ta viết không thể nào thiếu những hiệu ứng phụ quan trong như Ajax, giao tiếp database,… FP sẽ làm một nhiệm vụ là cô lập và tách biết các hiệu ứng phụ ra. Nhằm mục đích giúp cho code của chúng ta rõ ràng. Và đặc biệt là dễ kiểm soát. Vì hiệu ứng phụ sẽ làm thay đổi giá trị biến hay cái gì đó bất cứ lúc nào.
Trên đây là hai tính chất, khẩu quyết đầu tiên trong Functional Programming. Đây là những tính chất cơ bản nhất. Và chắc chắn bắt buộc đối với những ai muốn dấn thân vào con đường này.
Higher-Order Function
Higher-order function (HOF) là một function có ít nhất một trong hai đặc điểm sau:
- Tham số đầu vào là một hoặc nhiều function
- Return một function
Nảy giờ chúng ta đã lấy ví dụ về Javascript nhiều rồi. Bây giờ là ví dụ PHP về Higher-order function.
<?php /* Hàm twice có tham số đầu vào là một function và return một function */ $twice = function($add, $number) { return $add($add($number)); }; $add = function($number) { return $number + 1; }; echo($twice($add, 5));
Function twice
vừa có tham số đầu vào là một function vừa return một function. Cho nên hàm này thừa sức để làm một Higher-order function.
Khẩu quyết thứ ba này rất đơn giản và dễ hiểu. Nhưng có một câu hỏi được đặt ra. Tại sao phải dùng Higher-order function?
FP như giới thiệu ban đầu! Là một kiểu lập trình theo phong cách nhảy múa cùng với function. Higher-order function giúp cho chúng ta dễ dàng làm được việc này. Mọi flow, ngóc ngách, mọi dòng chảy code, chúng ta dễ dàng control chúng với HOF.
Giới thiệu về Functional Programming tới đây thôi. Vì như mình đã nói từ đầu bài. FP là một con đường dài.
FP còn rất nhiều khẩu quyết phía sau như: Function Composition, Currying function, Object Composition, không có new/this, class citizens,…
#3 Functional Programming và OOP
Functional programming | OOP |
Lấy function là đơn vị thao tác cơ bản | Thao tác dựa trên các concept của object |
Sử dụng Immutable data | Sử dụng Mutable data |
Theo mô hình declarative programming | Theo mô hình imperative programming |
Hổ trợ Parallel programming | Không hổ trợ Parallel programming |
Function không chưa side effects | Method có chứa side effects |
Flow control sử dụng việc gọi hàm hay sử dụng đệ quy | Flow control sử dụng vòng lặp hay câu lệnh if |
Thứ tự thực hiện các câu lệnh không quan trọng | Thứ tự thực hiện các câu lệnh cực kì quan trọng |
Hổ trợ cả “Abstraction over Data” và “Abstraction over Behavior”. | Chỉ hổ trợ “Abstraction over Data”. |
#Kết
Bài viết này, mình chỉ trình bày sơ bộ về functional programming. Và so sánh đôi chút nó với lập trình hướng đối tượng.
Đương nhiên chỉ với giới hạn một bài viết. Không thể cover hết tinh hoa của functional programming. Mình để những tinh hoa đó cho chúng ta cùng nhau research.
Functional programming đòi hỏi người luyện phải chuyên tâm, kiên trì. Không phải ngày một ngày hai mà có thể luyện thành.
Chúc các bạn thành công trên con đường tu luyện với Functional Programming!
Xem thêm: