#Lập trình hàm là gì
Trong ngành khoa học máy tính, lập trình hàm là một mô hình lập trình xem việc tính toán là sự đánh giá các hàm toán học và tránh sử dụng trạng thái và các dữ liệu biến đổi. Lập trình hàm nhấn mạnh việc ứng dụng hàm số, trái với phong cách lập trình mệnh lệnh, nhấn mạnh vào sự thay đổi trạng thái. Lập trình hàm xuất phát từ phép tính lambda, một hệ thống hình thức được phát triển vào những năm 1930 để nghiên cứu định nghĩa hàm số, ứng dụng của hàm số, và đệ quy. Nhiều ngôn ngữ lập trình hàm có thể được xem là những cách phát triển giải tích lambda.
Đặc điểm của ngôn ngữ lập trình hàm
- Giá trị không thay đổi Một khi giá trị của biến được thiết lập, thì nó sẽ không bị thay đổi.
- Không tác dụng lề (side-effect) Khi truyền tham số cho hàm, 1 hàm luuon trả về 1 kết quả duy nhất có nghĩa là kết quả của hàm chỉ phụ thuộc vào các tham số đầu vào chứ không phụ thuộc vào trạng thái của chương trình
- Hỗ trợ hàm bậc cao Hàm bậc cao là hàm sử dụng hàm khác như 1 tham số hoặc trả về kết quả là 1 hàm. Đây là 1 điểm đặc trưng của những ngôn ngữ thuần lập trình hàm
- Cho phép kĩ thuật áp dụng bán phần (partial application) hay currying Kĩ thuật curying là một kỹ thuật trong đó hàm lần lượt sử dụng từng tham số của nó, mỗi lần sử dụng lại trả về một hàm mới và chấp nhận tham số tiếp theo. Kĩ thuật này cho phép chuyển 1 hàm nhiều tham số sang hàm ít tham số hơn.
- Đệ quy Hàm được gọi lại trong chính nó
- Hỗ trợ tính toán không chặt Tính toán chặt hay không chặt là những khái niệm chỉ cách xử lý thông số của hàm khi tính toán một biểu thức. Tính toán không chặt sẽ trì hoãn việc xử lí các giá trị cho đến khi nào nó thực sự cần thiết Ví dụ: print length([2+1, 3*2, 1/0, 5-4]) Trong tính toán chặt thì biểu thức trên sẽ trả về lỗi khi chương trình tính toán giá trị 1/0 Tuy nhiên, trong tính toán không chặt thì hàm vẫn trả về kết quả là 4 do bỏ qua việc tính toán giá trị các phần tử trong mảng
##Cách sử dụng ruby như 1 ngôn ngữ lập trình hàm
###Không update biến. Khi 1 biến được tạo ra thì không update nó.
Cụ thể: Không thực hiện append đối với string hay array
No:
indexes = [1, 2, 3]
indexes << 4
indexes # [1, 2, 3, 4]
Yes:
indexes = [1, 2, 3]
all_indexes = indexes + [4] # [1, 2, 3, 4]
Không update hash
No:
hash = {:a => 1, :b => 2}
hash[:c] = 3
hash
Yes:
hash = {:a => 1, :b => 2}
new_hash = hash.merge(:c => 3)
Không sử dụng những destructive method chứa dấu !
No
string = "hello"
string.gsub!(/l/, 'z')
string # "hezzo"
Yes:
string = "hello"
new_string = string.gsub(/l/, 'z') # “hezzo"
Không thực hiện phép toán chồng chất
No:
output = []
output << 1
output << 2 if i_have_to_add_two
output << 3
Yes:
output = [1, (2 if i_have_to_add_two), 3].compact
Không sử dụng lại biến
No
number = gets
number = number.to_i
Yes
number_string = gets
number = number_string.to_i
###Sử dụng block như các hàm bậc cao tuỳ theo các pattern
init-empty + each + push = map
No:
dogs = []
["milu", "rantanplan"].each do |name|
dogs << name.upcase
end
dogs # => ["MILU", "RANTANPLAN"]
Yes:
dogs = ["milu", "rantanplan"].map do |name|
name.upcase
end # => ["MILU", "RANTANPLAN"]
init-empty + each + conditional push -> select/reject
No:
dogs = []
["milu", "rantanplan"].each do |name|
if name.size == 4
dogs << name
end
end
dogs # => ["milu"]
Yes:
dogs = ["milu", "rantanplan"].select do |name|
name.size == 4
end # => ["milu"]
init + each + accumulate -> inject
No:
length = 0
["milu", "rantanplan"].each do |dog_name|
length += dog_name.length
end
length # => 14
Yes:
length = ["milu", "rantanplan"].inject(0) do |accumulator, dog_name|
accumulator + dog_name.length
end # => 14
Better
length = ["milu", "rantanplan"].map(&:length).inject(0, :+) # 14
###Thực hiện các lệnh rẽ nhánh theo phong cách lập trình hàm
No.
name = obj1.name
name = obj2.name if !name
name = ask_name if !name
Yes
name = obj1.name || obj2.name || ask_name
###Đánh giá không chặt Thay vì viết 1 đoạn code dài dòng như dưới đây dể tính toán tổng của 10 số tự nhiên dầu tiên thoả mãn bình phương của nó chia hết cho 5
n, num_elements, sum = 1, 0, 0
while num_elements < 10
if n**2 % 5 == 0
sum += n
num_elements += 1
end
n += 1
end
sum #=> 275
ta có thể viết lại ngắn gọn như sau bằng cách sử dụng method lazy
(1..Float::INFINITY).lazy.select { |x| x**2 % 5 == 0 }.take(10).inject(:+)
##Kết luận
Qua các ví dụ trên ta có thể thấy 1 số ích lợi của việc viết theo phong cách lập trinh hàm
- Ngắn gọn Bạn viét code ít hơn do các hàm có thể viết nối đuôi nhau trong khi phong cách lập trình mệnh lệnh không cho phép điều này.
- Có tính trừu tượng cao hơn, tính tái sử dụng cao hơn Mặc dù viết theo phong cách lập trình hàm yêu cầu bạn biết nhiều hàm hơn trong khi không thực sự nắm rõ bản chất bên trong hàm đó như thế nào nhưng điều đó lại giúp đoạn code dễ đọc dễ test hơn
- Dễ hiểu hơn Khi nhìn 1 đoạn code viết theo phong cách mệnh lệnh bạn có thể thấy khó hiểu hơn, tốn thời gian hơn, trong khi phiên bản theo phong cách lập trình hàm viết rõ ràng rành mạch hơn