Menu

Hàm trong R: định nghĩa, đối số và giá trị trả về

Cách tạo hàm trong R: cú pháp function(x) { }, giá trị trả về, đối số mặc định và đối số có tên, dấu ..., hàm ẩn danh, và cách phạm vi hoạt động.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Định nghĩa và gọi một hàm

Hàm (function) trong R là một giá trị, giống như một con số hay một chuỗi. Bạn tạo hàm bằng từ khóa function và gắn nó vào một cái tên bằng <-, y như cách bạn lưu bất cứ thứ gì khác vào một biến:

Các thành phần: function(x) khai báo một tham số tên x, cặp ngoặc nhọn chứa thân hàm, và lời gọi square(4) chạy thân hàm với x bằng 4. Với thân hàm chỉ một dòng, ngoặc nhọn là không bắt buộc (square <- function(x) x^2 là cùng một hàm), nhưng khi thân hàm dài ra thì có ngoặc nhọn sẽ dễ đọc hơn.

Vì hàm chỉ là một giá trị, mọi điều bạn biết về biến vẫn đúng: bạn có thể gán lại nó, truyền nó cho một hàm khác, hoặc lưu nó trong một list. Chỉ riêng điều đó đã làm nên một nửa cách viết R chuẩn mực, kể cả toàn bộ họ apply.

Giá trị trả về: biểu thức cuối cùng quyết định

Hàm R trả về kết quả của biểu thức được tính cuối cùng. Không có câu lệnh return bắt buộc:

return() vẫn tồn tại, và hoạt động đúng như bạn nghĩ: dừng hàm ngay lập tức và trả về đối số của nó. R viết đúng kiểu dành nó cho việc thoát sớm: dừng ngay ở đầu hàm khi một đầu vào khiến phần việc còn lại trở nên vô nghĩa. Kết hợp nó với các phép kiểm tra if-else:

Viết return(a / b) ở dòng cuối cũng không sai, chỉ thừa. Hãy để dành return() cho các lối thoát sớm, và để biểu thức cuối cùng lo cho luồng bình thường.

Giá trị mặc định và đối số có tên

Một tham số có thể mang giá trị mặc định, khai báo bằng = trong danh sách tham số. Người gọi chấp nhận giá trị mặc định thì chỉ cần bỏ đối số đó đi:

Lời gọi thứ ba cho thấy nửa còn lại của câu chuyện: đối số có tên. Khi bạn viết name = "Ada" ở chỗ gọi, vị trí không còn quan trọng: R khớp theo tên trước, rồi điền các tham số còn lại theo vị trí. Từ đó rút ra hai quy tắc thực tế:

  • Đặt các tham số bắt buộc lên trước, các tham số có mặc định ở sau, để lời gọi theo vị trí vẫn tự nhiên.
  • Ở chỗ gọi, hãy ghi tên cho mọi đối số không hiển nhiên theo vị trí. greet("Ada", punctuation = "?") rõ ràng hơn là bắt người đọc đếm dấu phẩy.

Chuyển tiếp đối số bằng ...

Tham số đặc biệt ... (đọc là "dots") có nghĩa là "bao nhiêu đối số thêm cũng được". Một hàm khai báo ... có thể nhận những đối số nó chưa từng liệt kê và chuyển tiếp chúng cho một hàm khác:

shout() không biết và cũng không quan tâm bạn truyền bao nhiêu từ: nó giao hết cho paste(). Để ý rằng punctuation đứng sau dấu chấm: mọi tham số khai báo sau ... chỉ có thể đặt bằng tên, đúng là điều bạn muốn cho các tùy chọn. Mẫu chuyển tiếp này có mặt khắp base R: sapply(x, round, digits = 2) chạy được vì sapply chuyển digits = 2 cho round qua ... của chính nó.

Cái giá của dấu chấm là lỗi chính tả lọt qua mà không ai hay: gõ nhầm punctuation thành punctation thì nó chỉ bị nuốt vào ... thay vì báo lỗi. Hãy giữ các hàm dùng dấu chấm thật nhỏ gọn.

Hàm ẩn danh

Khi một hàm chỉ cần dùng một lần, thường là làm đối số cho một hàm khác, bạn có thể bỏ hẳn tên:

Hai dòng làm cùng một việc. function(x) x^2 là cách viết cổ điển; \(x) x^2 là cách viết tắt có từ R 4.1, trong đó \ thay cho chữ function. Chúng giống hệt nhau về mọi mặt ngoại trừ số phím phải gõ: cách viết tắt không phải là một loại hàm khác.

Dùng hàm ẩn danh khi thao tác chỉ là một dòng gắn với một chỗ gọi duy nhất. Ngay khi bạn dán cùng một lambda vào hai chỗ, hoặc nó dài quá một dòng, hãy đặt tên cho nó: hàm có tên thì kiểm thử được và tự giải thích được.

Phạm vi: hàm nhìn thấy những gì

R dùng phạm vi từ vựng (lexical scoping): khi một hàm cần một biến mà nó không tìm thấy trong tham số và các phép gán cục bộ của mình, nó tìm trong môi trường nơi hàm được định nghĩa, không phải nơi hàm được gọi. Trên thực tế, điều đó có nghĩa là hàm có thể đọc các biến từ script xung quanh:

Mặt ngược lại: phép gán bên trong hàm không bao giờ lọt ra ngoài. rate <- 0.5 bên trong change_inside tạo một biến cục bộ mới, biến này mất đi khi hàm trả về; rate bên ngoài vẫn in ra 0.1. Đây là một tính năng, vì hàm không thể vô tình giẫm lên workspace của bạn, nhưng nó làm người mới bất ngờ khi họ tưởng phép gán bên trong sẽ được giữ lại. Nếu một hàm cần đưa kết quả ra ngoài, hãy trả nó về rồi gán ở chỗ gọi. (Có toán tử <<- vươn ra bên ngoài; hãy xem nó là dấu hiệu code có vấn đề cho đến khi bạn thật sự cần đến.)

Một ví dụ hoàn chỉnh

Đây là mẫu đáng giá nhất: nhận ra mình đang lặp lại ba dòng phân tích, bọc chúng vào một hàm, rồi dùng lại. Giả sử bạn liên tục tóm tắt các vector số đo:

Mọi thứ trong trang này đều có ở đó: tham số digits có mặc định, một lời gọi có tên ghi đè nó, và biểu thức cuối cùng (round(out, digits)) đảm nhận việc trả về. Hàm đọc như một câu văn, chạy với mọi vector số, và chỉ nằm ở một chỗ khi bạn quyết định thêm trung vị.

Những điều cần nhớ

  • name <- function(args) { body } tạo một hàm; biểu thức được tính cuối cùng là giá trị trả về.
  • return() dành cho việc thoát sớm, không phải cho dòng cuối bình thường.
  • Giá trị mặc định khai báo bằng = trong danh sách tham số; đối số có tên giúp chỗ gọi không phụ thuộc thứ tự và dễ đọc.
  • ... nhận và chuyển tiếp mọi đối số thêm: rất mạnh, nhưng che giấu lỗi chính tả.
  • function(x) và \(x) là một; dùng hàm ẩn danh cho những thao tác một dòng dùng một lần.
  • Hàm đọc biến từ nơi nó được định nghĩa; phép gán bên trong hàm ở lại bên trong.

Tiếp theo: họ apply, những hàm nhận hàm của bạn và chạy nó trên cả vector và list.

Câu hỏi thường gặp

Tạo hàm trong R như thế nào?

Gán một đối tượng function cho một cái tên: square <- function(x) { x^2 }. Các tham số nằm trong ngoặc tròn, thân hàm nằm trong ngoặc nhọn, và bạn gọi nó như square(4). Biểu thức được tính cuối cùng chính là giá trị hàm trả về.

Trong R có cần return() không?

Không. Hàm R tự động trả về giá trị của biểu thức cuối cùng nó tính. return() vẫn có, và R viết đúng kiểu dành nó cho việc thoát sớm: dừng ngay ở đầu hàm khi đầu vào không hợp lệ hoặc khi một trường hợp biên đã được xử lý.

Hàm ẩn danh trong R là gì?

Là hàm bạn định nghĩa mà không đặt tên, thường được truyền thẳng vào một hàm khác: sapply(x, function(v) v^2). Từ R 4.1 còn có cách viết tắt \(v) v^2, nghĩa y hệt nhưng gõ ít hơn.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu