Lập trình hàm (Functional Programming) là một khái niệm, mô hình lập trình thú vị được chú ý nhiều gần đây.
Lập trình Hàm với ví dụ Python
Bài viết này trình bày một số khía cạnh quan trọng nhất của Lập trình hàm nói chung qua các Ví dụ Lập trình hàm trong Python.
Lập trình hàm là một loại mô hình lập trình mà các hàm biểu diễn quan hệ giữa các đối tượng, giống như trong toán học.
Vì vậy, bạn sẽ thấy các hàm (functions) xuất hiện nhiều đáng kể.
Mô hình lập trình này có thể được thực hiện bằng nhiều ngôn ngữ. Có một số ngôn ngữ sinh ra đã dựa theo mô hình lập trình hàm như Closure, Erlang hoặc Haskel.
Nhưng cũng có nhiều ngôn ngữ khác cũng hỗ trợ lập trình hàm như: C ++, C#, F#, Java, Python, JavaScript và các ngôn ngữ khác.
Trong bài viết này, bạn sẽ tìm thấy giải thích về một số nguyên tắc và khái niệm quan trọng liên quan đến Lập trình Hàm và sử dụng ngôn ngữ Python:
1. Prue functions là gì?
Prue funtions là một hàm thuần túy:
-
Hàm bình thường: Trả về cùng một kết quả nếu cung cấp cùng một đối số
Nếu như một hàm sử dụng một đối tượng từ phạm vi cao hơn hoặc số ngẫu nhiên, giao tiếp với các tệp, v.v., nó có thể không thuần túy vì kết quả của nó không chỉ phụ thuộc vào các đối số của nó.
Một hàm sửa đổi các đối tượng bên ngoài phạm vi của nó, ghi vào tệp, in ra console, v.v., có tác dụng khác cũng có thể không thuần túy.
Các hàm thuần túy thường không sử dụng các đối tượng từ các phạm vi bên ngoài và do đó tránh các trạng thái chia sẻ.
Điều này có thể đơn giản hóa một chương trình và giúp thoát khỏi một số lỗi.
2. Anonymous Functions là gì?
Các hàm ẩn danh (Anonymous Funtion hay còn được gọi là lambda) có thể rất thuận tiện cho các cấu trúc lập trình hàm.
Chúng không có tên và thường được tạo ra với một mục đích duy nhất.
Trong Python, bạn tạo một hàm ẩn danh với từ khóa lambda:
lambda x, y: x + y
Câu lệnh trên tạo ra hàm chấp nhận hai đối số và trả về tổng của chúng.
Trong ví dụ tiếp theo (chạy trên terminal), các hàm f và g thực hiện cùng một việc:
>>> f = lambda x, y: x + y
>>> def g(x, y):
return x + y
Nếu bạn chạy trên Editor / hoặc IDE thì bạn có thể viết như sau:
# Khai báo hàm lambda
f = lambda x, y: x + y
# Khai báo hàm thông thường
def g(x, y):
return x + y
# Gọi hàm
print(f(3, 4))
print(g(3, 4))
3. Recursive Functions
Recursive Functions (Hay còn được gọi là Hàm đệ quy) là một hàm tự gọi chính nó trong quá trình thực thi.
Nếu bạn mới nghe đệ quy lần đầu thì bạn có thể xem hình ảnh minh họa sau:
Minh họa về hàm đệ quy (Recursive Function)
Ví dụ: Chúng ta có thể sử dụng đệ quy để tìm giai thừa theo kiểu lập trình hàm:
>>> def factorial_r(n):
if n == 0:
return 1
return n * factorial_r(n - 1)
Ngoài ra, chúng ta có thể giải quyết vấn đề tương tự bằng cách sử dụng vòng lặp trong Python (for hoặc while):
>>> def factorial_l(n):
if n == 0:
return 1
product = 1
for i in range(1, n+1):
product *= i
return product
4. First Class Functions
Trong lập trình hàm, các hàm là các first-class object, còn được gọi là các hàm bậc cao hơn (high-order functions) - Các kiểu dữ liệu được xử lý giống như các kiểu khác.
Các hàm (hoặc, chính xác hơn, các con trỏ hoặc tham chiếu của chúng) có thể được truyền dưới dạng đối số và được trả về từ các hàm khác.
Chúng cũng có thể được sử dụng như các biến trong các chương trình.
Đoạn mã dưới đây minh họa việc truyền hàm max (built-in function) dưới dạng đối số của hàm f và gọi nó từ bên trong f.
>>> def f(function, *arguments):
return function(*arguments)
>>> f(max, 1, 2, 4, 8, 16)
16
Các Khái niệm lập trình hàm rất quan trọng là:
Chúng đều được hỗ trợ trong Python.
4.1. Mapping
Mapping được thực hiện với class map được tích hợp sẵn (built-in class).
Nó lấy một hàm (hoặc phương thức hoặc bất kỳ thứ gì có thể gọi) làm đối số thứ nhất và iterable (như list hoặc tuple) làm đối số thứ hai và trả về iterator với kết quả gọi hàm trên các item của iterable.
Iterator là các đối tượng cho phép ta lấy từng phần tử của nó, hành động này có thể được lặp đi lặp lại.
>>> list(map(abs, [-2, -1, 0, 1, 2]))
[2, 1, 0, 1, 2]
Trong ví dụ này, hàm abs (built-in function) được gọi với các đối số -2, -1, 0, 1 và 2, tương ứng.
Chúng ta có thể có được kết quả tương tự với kỹ thuật comprehension như sau:
>>> [abs(item) for item in [-2, -1, 0, 1, 2]]
[2, 1, 0, 1, 2]
Chúng ta cũng có thể không phải sử dụng các hàm tích hợp.
Thay vào đó, có thể cung cấp một hàm tùy chỉnh riêng (hoặc phương thức).
Ở trường hợp này, các hàm Lambda có thể đặc biệt thuận tiện:
>>> list(map(lambda item: 2 * item, [-2, -1, 0, 1, 2]))
[-4, -2, 0, 2, 4]
Câu lệnh trên đã nhân mỗi item của list [-2, -1, 0, 1, 2] với 2 bằng cách sử dụng hàm lambda tùy chỉnh lambda item: 2*item.
Tất nhiên, chúng ta có thể sử dụng sự kỹ thuật comprehension để đạt được kết quả tương tự:
>>> [2 * item for item in [-2, -1, 0, 1, 2]]
[-4, -2, 0, 2, 4]
4.2. Filtering
Filtering được thực hiện với class filter (built-in class)
Nó cũng lấy một hàm (hoặc phương thức, hoặc bất kỳ lệnh gọi nào) làm đối số thứ nhất và Iterables làm đối số thứ hai.
Nó gọi hàm trên các item của iterable và trả về một iterable mới với các item mà hàm trả về True hoặc bất cứ thứ gì được đánh giá là True. Ví dụ:
>>> list(filter(lambda item: item >= 0, [-2, -1, 0, 1, 2]))
[0, 1, 2]
Câu lệnh trên trả về list mới với các item > 0 của list [-2, -1, 0, 1, 2], như được định nghĩa với hàm lambdas item: item > 0.
Một lần nữa, chúng ta có thể có kết quả tương tự bằng kỹ thuật comprenhension:
>>> [item for item in [-2, -1, 0, 1, 2] if item >= 0]
[0, 1, 2]
4.3. Reducing
Reducing được thực hiện với hàm reduce từ functools. Một lần nữa, nó cần hai đối số: một hàm và iterables.
Nó gọi hàm trên hai item đầu tiên của iterables, sau đó là gọi hàm trên kết quả này và item thứ 3. v.v.
Cuối cùng, nó trả về một giá trị duy nhất.
Ví dụ: Chúng ta có thể tìm thấy tổng của tất cả các item trong danh sách như thế này:
>>> import functools
>>>
>>> functools.reduce(lambda x, y: x + y, [1, 2, 4, 8, 16])
31
Ở đây:
-
Lấy 1 + 2 bằng 3
-
Sau đó lấy 3 + 4 bằng 7
-
Tiếp tục lấy 7 + 8 bằng 15
-
Cuối cùng lấy 15 + 16 bằng 31
Ví dụ này chỉ để minh họa về Reduce.
Còn cách tính tổng các item trong thường được sử dụng trong Python là:
>>> sum([1, 2, 4, 8, 16])
31
Bởi vì Python ưu tiên khả năng dễ đọc. Tính tổng các item trong bằng reduce thì quá khó hiểu so với hàm sum.
5. Immutable Data Types: Kiểu dữ liệu bất biến
Một đối tượng bất biến (immutable object) là một đối tượng mà trạng thái của nó không thể được sửa đổi khi nó được tạo ra.
Ngược lại, một đối tượng biến đổi (mutable object) có thể thay đổi, nó cho phép thay đổi trạng thái của nó.
Nói chung, đối tượng bất biến được kỳ vọng ở trong Lập trình Hàm.
Ví dụ: Trong Python, các list có thể thay đổi, trong khi các tuple là bất biến:
>>> a = [1, 2, 4, 8, 16] >>> a[0] = 32 # OK. Bạn có thể sửa đổi lists.
>>> a
[32, 2, 4, 8, 16]
>>> a = (1, 2, 4, 8, 16)
>>> a[0] = 32 # Bạn không thể sửa đổi tuples.
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
Chúng ta có thể sửa đổi list bằng cách thêm cho chúng các phần tử mới.
Nhưng khi chúng ta cố gắng thực hiện điều này với tuple, chúng không bị thay đổi nhưng các trường hợp mới được tạo:
>>> # Lists (Biến đổi)
>>> a = [1, 2, 4] # a là một list
>>> b = a # a và b tham chiếu đến cùng một list
>>> id(a) == id(b)
True
>>> a += [8, 16] # a được sửa đổi và b cũng thế - Vì chúng tham chiếu đến cùng list
>>> a
[1, 2, 4, 8, 16]
>>> b
[1, 2, 4, 8, 16]
>>> id(a) == id(b)
True
>>>
>>> # Tuples (Bất biến)
>>> a = (1, 2, 4) # a là một tuple
>>> b = a # a và b cùng tham chiếu đến cùng một tuple
>>> id(a) == id(b)
True
>>> a += (8, 16) # một tuple mới được tạo và gán cho a; b không thay đổi
>>> a # a tham chiếu đến một đối tượng mới
(1, 2, 4, 8, 16)
>>> b # b tham chiếu đến đối tượng cũ
(1, 2, 4)
>>> id(a) == id(b)
False
Ưu điểm của Lập trình hàm
Các khái niệm và nguyên tắc cơ bản, đặc biệt là các high-order function, immutable data và việc không có tác dụng khác đã phần nào nói lên những ưu điểm của lập trình hàm.
-
Chúng có thể dễ dàng hiểu hơn, triển khai, kiểm thử và gỡ lỗi.
-
Chúng có thể ngắn hơn và súc tích hơn (ví dụ so sánh hai chương trình để tính giai thừa ở trên).
-
Chúng có thể ít bị lỗi hơn.
-
Chúng dễ dàng làm việc hơn khi thực thi song song.
Qua ví dụ về Lập trình hàm với Python, chúng ta thấy rằng: Lập trình hàm là một mô hình lập trình có giá trị đáng học hỏi.
Ngoài những ưu điểm được liệt kê ở trên, nó có thể cung cấp cho bạn một góc nhìn mới trong việc giải quyết các vấn đề lập trình.
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp #python #java #php