Hiện giờ mình đang học bên python về phần hướng đối tượng. Rồi cũng lên mạng tìm hiểu thêm về phần hướng đối tượng là gì thì có một số trang nói qua phần lập trình hàm nhưng phần này mình chưa nghe đến (từ lúc học python đến giờ) và cũng không hiểu lắm. Mong các bác có thể giúp mình “thông” phần này!
Hỏi về lập trình hướng đối tượng và lập trình hàm?
Lập trình hàm là… viết hàm.
def fun(n):
print("Python is fun.\n" * n)
n = int(input()) # Python 3, tương đương với raw_input trong Python 2
fun(n)
Lambda calculus không phải hàm này.
Có thể dùng currying để minh họa:
Ta bắt đầu với hàm cơ bản: f: x -> x-1. Nhưng chỉ trừ 1 thì quá tù nên ta đặt ra họ hàm f_y(x): x -> x-y với y là hằng. Đặt g: y -> f_y(x), khai triển thành g: y -> (x -> x-y).
Học Haskell đi bạn, Haskell được phát triển để thống nhất khái niệm ngôn ngữ lập trình hàm đó. Học xong sẽ thấy các ngôn ngữ khác vay mượn nó nhiều.
Tài liệu: http://learnyouahaskell.com
haskell có khó ko bạn? mình nghe đồn là vậy. Mình thì chỉ mới học mỗi python,html,css và mình định theo bên web dev.
._. là sao ấy bạn nhỉ?
Lập trình hàm chỉ là tên đại diện cho nhiều khái niệm khác nhau: pure function, first class function, function composition, immutable Data type và đặc biệt là functor và monad.
Mình gợi ý Haskell vì nó implement tất cả các khái niệm. Các ngôn ngữ hàm khác không hiện thực hết tất cả khái niệm nên không có cái nhìn toàn diện về lập trình hàm.
Trên mạng có nhiều bài nói về sự ra đời của lập trình hàm. Tớ tham khảo 1 số tài liệu thì lập trình hàm ra đời trước oop. Tư duy 2 cái này khác nhau khá nhiều. Tuy nhiên đang ở trường đại học thì nên học oop trước.
Sau nhiều thời gian, cho đến hiện tại mình vẫn không hình dung được lập trình hàm nó khác lập trình thủ tục và hướng đối tượng chỗ nào.
Anh em nào có thể cho một đoạn chương trình tính tổng 2 số để thấy sự khác nhau giữa lập trình hàm và thủ tục trong C được không ?
C:
int sum(int a, int b){
return a + b;
}
int res = sum (1,2);
Trong lập trình hàm, hàm có thể được tạo ra ở bất cứ chỗ nào chạy lệnh được (như một biểu thức thay vì một khai báo). Trong OOP hàm không thể đứng riêng một mình vì không có khái niệm này chỉ có đối tượng và hành vi của đối tượng mà thôi.
Hàm trong C có thể tiếp nhận con trỏ hàm (điển hình là qsort
) nhưng không có khái niệm lambda.
Nếu như giải thích thì những ngôn ngữ có lambda expression thì cũng chả khác mấy và gần như đều viết được static method nằm ngoài đối tượng.
Với những ngôn ngữ như C, cũng chỉ khác nhau ở nơi đặt code thực thi. Một đằng viết tại nơi chạy và một đằng viết ở ngoài rồi gọi tại vị trí chạy. Hiệu quả và kết quả cũng đâu khác nhiều ?
Nhưng bạn không thể tạo ra một hàm mới trong runtime (import thì được).
Có thể viết lại ví dụ currying:
f :: Num -> Num
f x = x + 1
Ta có hàm trả về số cộng thêm 1. Tuy nhiên cộng thêm 1 thì quá “tù” nên ta đặt ra họ hàm add_y(): x -> x+y với y là con số.
type AddY = Num -> Num
g :: Num -> AddY
và cuối cùng ta có
g :: Num -> AddY
g y = (\x -> x+y) # lambda expression
Khi gọi g
sẽ tạo 1 hàm mới trong runtime nhận tham số là x và tham số ẩn là y.
có lẽ lập trình hàm là loại bỏ hoàn toàn side effect trong lập trình thủ tục :V
ví dụ hàm swap trong C viết là:
void swap(int * a, int * b) { ... }
thì sẽ có side effect: sau khi gọi swap a và b thay đổi giá trị :V
lập trình hàm có thể viết là
typedef struct pair_type { const int first, last; } pair_t;
const pair_t swap(const int a, const int b) {
const pair_t result = {.first = b, .last = a};
return result;
}
sort thì trả về 1 cái mảng mới mà ko thay đổi mảng cũ :V
nói chung là ko có “biến” số mà chỉ có hằng số :V 1 khi đã gán ko thể thay đổi :V Các tham số truyền vào hàm toàn là hằng số :V :V
lợi thế của lập trình hàm là ko có side effect nên ko sợ race condition nên code parallel vô tư :V
Nếu bạn có thời gian và thực sự muốn tìm hiểu về lập trình hàm thì Haskell là một ngôn ngữ tuyệt vời để học. Nhưng ngoài nghiên cứu và muốn học một mô hình lập trình mới ra thì ngôn ngữ này không được mọi người sử dụng nhiều trong ứng dụng thực tế và một điểm quan trọng nữa là ngôn ngữ này khá khó.
lập trình hướng đối tượng hay lập trình thủ tục nếu cũng không thay đổi shared state thì cũng parallel vô tư mà. Nên thứ quyết định vẫn là kĩ năng của ltv thôi
Thực sự mình chưa thấy khác biệt nhiều.
Mình không hiểu “tạo hàm mới khi runtime” là như thế nào.
Nhưng có tạo được thì công việc của nó vẫn phải viết khi code. Một cái được viết tại nơi khác và gọi, một cái được viết trực tiếp tại nơi gọi, cũng khác nhau mấy đâu ?
Phần lớn các khái niệm trong lập trình hàm như Purity, Immutability, Refactoring, Higher-Order Functions, Closures, … đều có thể áp dụng trong các ngôn ngữ khác nhưng cách thực hiện quá phức tạp, phải lặp lại. Trong lập trình hàm, hàm là first-class citizen nên những khái niệm trên rất dễ áp dụng, và dễ nhìn
//hàm add = x + y
add x y = x + y
//hàm add10 = 10 + x
add10 x = add 10 x
//hàm do_sth_with10 nhận hàm f là hàm có 2 tham số (number) bất kì và x
do_sth_with10 f x = f 10 x // do_sth_with10 add 10 = 20
Vậy điểm đầu tiên khác biệt giữa lập trình hàm và bọn còn lại là có thể khai báo hàm ngay tại nơi gọi thay vì phải viết một hàm riêng ở bên ngoài và gọi ?
Nó có vẻ giống với lambda expression ?
Sự khác nhau giữa lập trình hàm và thủ tục là khác nhau về paradigm, hiểu nôm na là mind-set của người lập trình viên thôi.
Lập trình hàm (functional programming) là 1 nhánh của declarative programming ( DP - lập trình khai báo) khác với thủ tục, OOP,… nằm trong nhánh của imperative programming (IP - lập trình mệnh lệnh). [1]
Trong IP thì lập trình viên thường viết code theo 1 trình tự hay control flow - hiểu theo nghĩa của người lập trình nhé - để thay đổi states. Trái ngược với nó, trong DP lập trình viên không định nghĩa flow cho chương trình, công việc của lập trình viên là viết code sao cho trả lời được kết quả là gì?
Trên wiki cũng nói, IP = trả lời câu hỏi how, DP = what. Ví dụ:
def odd_procedure(l):
for i in l:
if i % 2 != 0:
result.append(i)
odd_functional = lambda x: x % 2 == 1
trong odd_procedure
thì lập trình viên đi theo luồng suy nghĩ, viết code để thực hiện công việc how rất tự nhiên: lặp qua từng phần tử, lọc những phần tử có mod 2 = 1,…
còn ở odd_functional
thì lập trình viên chỉ đưa ra đáp số cho máy tính thay vì như trong odd_procedure. Dĩ nhiên đây là cách người lập trình suy nghĩ, việc code execution như thế nào thì phụ thuộc vào implementation của từng ngôn ngữ lập trình.
Thực ra là có gì không làm được nhỉ.
Dạo này mình dùng Java nhiều nên ví dụ thế thôi.
BiFunction myFunction = (x, y) -> {...};//Phần trong hàm hoàn toàn có thể tuỳ biến theo input
myFunction.apply(1, "s");
Thậm chí mình có thể tạo ra runtime code luôn bằng cách gọi compile một String thành một file .class rồi import chính class đó.