Một số khái niệm cơ bản trong ngôn ngữ hàm

Một phần của tài liệu Nghiên cứu lập trình phản ứng với ngôn ngữ ELM (Trang 25 - 29)

Khái niệm hàm

Hàm là khái niệm cơ bản trong các ngôn ngữ hàm. Một hàm có thể nhận từ không đến nhiều tham đối vào để tính toán và trả về một giá trị, giá trị này chỉ phụ thuộc vào các tham đối đã nhận mà thôi. [1]

Trong Elm một hàm đƣợc định nghĩa bởi hai phần: Phần khai báo và phần định nghĩa hàm. Phần khai báo có thể vắng mặt có dạng một nguyên mẫu hàm:

<tên hàm> : <miền xác định> - > <miền giá trị>

Phần định nghĩa hàm có dạng một phƣơng trình, gồm vế trái và một số vế phải, mỗi vế phải có thể có một điều kiện đóng vai trò «lính gác» (guard) phân biệt đứng cuối:

<tên hàm> [ <danh sách tham đối> ] = <biểu thức> [ <điều kiện> ]

Đệ quy

Vòng lặp trong các ngôn ngữ hàm thƣờng đƣợc thực hiện thông qua đệ quy. Hàm đệ quy sẽ tự gọi chính nó cho phép thực hiện đi thực hiện lại một tác vụ. Việc đệ quy có thể đòi hỏi phải sử dụng một chồng (stack) nhƣng đệ quy đuôi vẫn có thể đƣợc trình biên dịch nhận ra và tối ƣu hóa nó thành cùng đoạn mã đƣợc dùng để hiện thực vòng lặp trong ngôn ngữ mệnh lệnh. Tiêu chuẩn của ngôn ngữ hàm là

26

phải nhận diện và tối ƣu hóa đƣợc đệ quy đuôi. Một trong những cách tối ƣu hóa đệ quy đuôi là chuyển chƣơng trình thành kiểu truyền liên tiếp trong quá trình dịch.

Các mẫu đệ quy phổ biến đều có thể đƣợc khử đệ quy bằng các hàm bậc cao, "fold" và "unfold" - gấp và mở gấp (catamorphism and anamorphism) là những ví dụ rõ nhất. Các hàm bậc cao nhƣ vậy đóng vai trò tƣơng tự nhƣ các cấu trúc điều khiển có sẵn nhƣ vòng lặp trong ngôn ngữ mệnh lệnh.

Ví dụ đoạn mã bằng Elm dƣới đây sẽ đảo ngƣợc một danh sách bằng hàm foldl (fold from the left) dùng để khử đệ quy.

daonguoc: List a -> List a

daonguoc list = foldl (::) [] list

main = show (daonguoc[1..9])

Xét đoạn mã trên ta thấy hàm đƣợc khai báo

daonguoc: List a -> List a Hàm đƣợc định nghĩa

daonguoc list = foldl (::) [] list

Hàm đệ quy ở trên đƣợc định nghĩa bằng hàm foldl. Hàm này chứa một hàm (::) có 2 giá trị đó là hàm [] một mảng trống và một tham đối list. Ý nghĩa hàm (::) là ghép phần tử của danh sách vào một mảng trống []. Giá trị của hàm sẽ đƣợc in ra bằng hàm main là: [9,8,7,6,5,4,3,2,1]

Chúng ta có thể thấy tính ngắn gọn của lời gọi đệ quy trong ngôn ngữ hàm, ngôn ngữ Elm sử dụng cú pháp ngắn gọn và không sử dụng các cặp dấu ngoặc để bao bọc các tham đối hoặc bao bọc các lời gọi hàm. Ta thấy rõ ràng lập trình hàm có thể tối ƣu hóa phép lặp so với các ngôn ngữ mệnh lệnh bằng cách bỏ các vòng lặp for và các lệnh gán biến.

27

Một yếu tố quan trọng khác của hầu hết các ngôn ngữ hàm là phƣơng pháp currying (lấy tên nhà logic học Haskell B. CURRY) hay còn đƣợc gọi là phƣơng pháp tham đối hoá từng phần (partial parametrization).

Thông thƣờng một hàm đƣợc thực hiện theo kết hợp trái nếu viết f x y (hàm f

tác động lên hai đối x y) hay viết quy ƣớc (x, y) f(x, y) thì cũng đƣợc xem nhƣ viết (f x) y nghĩa là kết quả của việc áp dụng f cho x là một hàm để áp dụng cho y.

Ta viết x -> (y -> f (x, y))

Một cách tổng quát cho hàm n biến

f (x1, x2,...xn): x1 -> (x2 -> (x3 ->...(xn -> f (x1, ...,xn)) ...)) Chẳng hạn xét hàm mult đƣợc định nghĩa nhƣ sau:

mult x y = x * y

Nếu hà m mult đƣợc gọi với hai đối số thì mult sẽ tính tích số của hai đối số này theo nghĩa thông thƣờng. Tuy nhiên trong ngôn ngữ lập trình hàm mult có thể đƣợc xem nhƣ hàm một tham đối (đối thứ nhất x) kết quả sẽ là một hàm khác áp dụng cho một tham đối (đối thứ hai y). Một cách tổng quát một hàm nào đó có nhiều hơn một tham đối có thể đƣợc tham đối hóa từng phần.

Khái niệm về bậc của hàm

Trong các ngôn ngữ hàm, hàm luôn luôn đƣợc xử lý nhƣ những đối tƣợng đƣợc truyền tham đối để trả về kết quả và đƣợc lƣu giữ trong các cấu trúc dữ liệu. Một hàm nhận một hàm khác nhƣ là một tham đối đƣợc gọi là hàm bậc cao. Các hàm bậc cao mang lại tính hiệu quả và là nền tảng (corner-stone) của lập trình hàm.

Hàm bậc cao là các hàm số hoặc có thể nhận các hàm số khác làm tham số hoặc có thể trả về kết quả là hàm số (phép toán vi phân d / dx để tính vi phân của hàm f là một ví dụ của hàm bậc cao trong giải tích).

Các hàm bậc cao có liên hệ chặt chẽ với hàm hạng nhất ở chỗ các hàm bậc cao và hàm hạng nhất đều cho phép nhận hàm số làm tham số và trả về các hàm khác. Sự khác biệt giữa hai loại này rất mờ nhạt: "bậc cao" mô tả một khái niệm

28

hàm trong toán học tính toán trong các hàm khác, còn "hàm hạng nhất" là một thuật ngữ của ngành khoa học máy tính mô tả các thực thể của ngôn ngữ lập trình trong đó không có giới hạn về việc sử dụng (vì vậy các hàm hạng nhất có thể xuất hiện ở bất cứ đâu trong chƣơng trình, giống nhƣ các thực thể hạng nhất khác nhau con số, trong đó có cả việc làm tham số cho các hàm khác và làm giá trị trả về của hàm khác).

Các hàm bậc cao cho phép áp dụng bán phần hoặc currying, 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. Việc làm này cho phép ngƣời lập trình biểu diễn một cách súc tích hàm số kế thừa, tƣơng tự nhƣ toán tử cộng sẽ lần lƣợt cộng từng số tự nhiên lại với nhau.

Tính toán chặt và không chặt

Có thể chia các ngôn ngữ hàm làm hai loại tùy vào việc chúng sử dụng cách tính toán biểu thức chặt (tham lam) hay không chặt (lƣời biếng) 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. Sự khác biệt về các cách tính toán này xuất hiện ở ngữ nghĩa biểu thị của biểu thức khi chúng có chứa phép toán lỗi hoặc có vấn đề. Khi tính toán chặt việc tính toán số hạng có chứa lỗi cũng sẽ dẫn đến lỗi. Ví dụ biểu thức: [11]

print length([2+1, 3*2, 1/0, 5-4])

Sẽ không tính đƣợc theo tính toán chặt vì phép chia không tại phần tử thứ 3 của danh sách. Còn với tính toán không chặt, hàm length sẽ trả về giá trị 4 vì khi tính toán hàm nó không cố gắng tính toán các phần tử trong danh sách. Nói một cách ngắn gọn tính toán chặt luôn luôn tính toán tất cả các số hạng của hàm trƣớc khi xử lý hàm. Tính toán không chặt không tính toán tham số của hàm trừ khi nó cần giá trị đó để tính toán hàm.

29

Cách hiện thực thông thƣờng của tính toán không chặt trong ngôn ngữ hàm là thu giảm đồ thị. Cách tính toán không chặt đƣợc dùng mặc định trong vài ngôn ngữ lập trình hàm thuần túy nhƣ Miranda, Clean và Haskell.

Một phần của tài liệu Nghiên cứu lập trình phản ứng với ngôn ngữ ELM (Trang 25 - 29)

Tải bản đầy đủ (PDF)

(79 trang)