Skip to content

Instantly share code, notes, and snippets.

@oNguyenThanhTung
Last active August 29, 2015 14:08
Show Gist options
  • Save oNguyenThanhTung/76066b2da7877880b48b to your computer and use it in GitHub Desktop.
Save oNguyenThanhTung/76066b2da7877880b48b to your computer and use it in GitHub Desktop.

Kĩ thuật lập trình hàm trong ruby

#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment