Tìm hiểu về Power Query / M Language (Part 4): Giá trị hàm, truyền hàm tính, hàm nội tuyến, hàm đệ quy
Tiếp nối bài viết trong phần 3 về định nghĩa hàm tính. chúng ta tiếp tục tìm hiểu về tính linh hoạt của hàm tính trong bài viết này như: giá trị của hàm, đệ quy, hàm trả về,…
Giá trị hàm ( Function Values )
Trong bài viết phần 3 chúng ta đã tìm hiểu có những hàm được mô tả là một biểu thức cuối cùng tạo ra một giá trị. Nhưng câu chuyện còn nhiều điều hơn thế: như là hàm là một bước trung gian cho một hàm hoặc là một biệu thức lớn hơn phức tạp hơn thế. Về mặt kỹ thuật một biểu thức hàm được đánh giá là một thứ gọi là giá trị hàm, giá trị này có thể được lưu giá trị trong các biến và được truyền vào và trả về từ các hàm khác. Việc gọi giá trị hàm thực thi phần thân của một hàm tạo ra giá trị trả về của hàm cuối cùng.
Bạn có thể nghĩ một đơn giản theo cách này:
– Biểu thức hàm là mã bạn đã viết.
– Giá trị hàm là sự biểu lộ kỹ thuật của biểu thức mà hàm bạn đã viết.
– Giá trị trả về của hàm là kết quả của việc thực thi hàm bạn đã viết.
let
NHAN = (a, b) => a * b,
KQ = NHAN(10, 20)
in
KQ
Ở ví dụ trên, biểu thức hàm (a, b) => a * b được định nghĩa thành một giá trị hàm được gán cho biến NHAN. Khi giá trị hàm này được gọi trên dòng tiếp theo (NHAN(10, 20)), phần thân của hàm được thực thi và giá trị mà nó tạo ra được trả về. Giá trị này sau đó được gán cho KQ.
Điều này nghe có vẻ phức tạp. Ví dụ trên đơn giản hơn rất nhiều so với những gì có thể mô tả nó.
nói về tính đon giản: Để giữ cho mọi thứ đơn giản, chúng ta thường bỏ qua từ “giá trị” khi nói về “giá trị hàm”. Khi một hàm mong đợi một giá trị hàm là một trong các tham số của nó, chúng ta thường chỉ nói rằng tham số đó là “được cho là một hàm” thay vì nói “được cho là một giá trị hàm” mặc dù sau đó thực sự là những gì được mong đợi. Điều tương tự cũng xảy ra khi chúng ta nói về “truyền các hàm xung quanh”. Về mặt kỹ thuật, chúng ta đang “truyền các giá trị hàm xung quanh” nhưng chúng ta thường gọi tắt với từ “giá trị”.
Truyền hàm tính ( Passing Functions )
Khả năng chuyển một hàm sang một hàm khác trong Power Query M rất mạnh mẽ. Hàm khác có thể triển khai một thuật toán (hoặc chức năng) chung có thể áp dụng rộng rãi, sau đó sử dụng hàm được truyền vào nó để điều chỉnh hành vi của nó sao cho phù hợp với tình huống cụ thể của chúng ta.
Ví dụ: thêm một cột mới vào bảng, Table.AddColumn triển khai công thức và truyển vào các tham số để tạo một cột mới kha thi theo mong muốn của chúng ta. Tuy nhiên chúng ta muốn điều chỉnh hành vi của Table.AddColumn để có thể kiểm soát các giá trị được sử dụng cho cột mới. Để cho phép chúng ta làm điều này, Table.AddColumn cho phép chúng ta chuyển nó thành một hàm dưới dạng tham số. Sau đó, nó gọi hàm này một lần cho mỗi hàng trong bảng, chuyển hàm đã truyền vào hàng hiện tại làm đối số của nó và sau đó sử dụng giá trị mà nó trả về làm giá trị cho cột mới cho hàng hiện tại.
let
Source = #table( {"Col1", "Col2"}, { {1, 2}, {3, 4} } ),
ColumnCreator = (row) => row[Col1] + row[Col2],
AddColumn = Table.AddColumn(Source, "RowTotal", ColumnCreator)
in
AddColumn
Vì dụ trên. Table.AddColumn xử lý việc thêm một cột mới (thuật toán chung). Chúng ta tùy chỉnh hành vi của nó thông qua chức năng mà chúng ta cung cấp được gọi trên cơ sở khi cần thiết (trong trường hợp này là một lần trên mỗi hàng). Chúng ta không phải viết một hàm xử lý tất cả các trách nhiệm liên quan đến việc thêm một cột mới, chỉ cần một hàm đơn giản nhận một hàng và tạo ra một giá trị duy nhất.
Trong một số ngôn ngữ khác, có thể đạt được hiệu ứng tương tự bằng cách sử dụng ủy quyền (delegates) hoặc con trỏ (pointers) hàm.
Hàm nội tuyến ( Inline Definition )
Vì một hàm là một biểu thức và các biểu thức được cho phép trong danh sách các tham số, chúng ta có thể xác định hàm nội tuyến, trực tiếp trong danh sách các tham số.
Dưới đây, hàm cột mới được định nghĩa trong danh sách đối số thay vì được gán cho một biến trước. Theo như Table.AddColumn có liên quan, hiệu ứng giống như ví dụ trên.
let
Source = #table( {"Col1", "Col2"}, { {1, 2}, {3, 4} } ),
AddColumn = Table.AddColumn(Source, "RowTotal", (row) => row[Col1] + row[Col2])
in
AddColumn
Sử dụng Shortcuts: each & _
Trong Power Query M, mỗi thứ cũng đặc biệt. Vì nó đơn giản hóa một mẫu mã M phổ biến.
Việc xác định một hàm chấp nhận một đối số là nhu cầu phổ biến trong Power Query M, ngôn ngữ này xác định một phím tắt để đơn giản hóa nó: Mỗi từ khóa là viết tắt của (_) =>.
Ví dụ dưới đây [FieldName] không có tên ngay trước khi viết tắt cho _[FieldName]. Mỗi câu lệnh kế tiếp sử dụng cú pháp ngắn gọn hơn giúp dễ đọc hơn.
(_) => _[Col1] + _[Col2]
each _[Col1] + _[Col2]
each [Col1] + [Col2]
Chúng ta có thể đơn giản hóa ví dụ định nghĩa nội tuyến trên bằng cách sử dụng các phím tắt này. Như thế nào cho ngắn gọn?
let
Source = #table( {"Col1", "Col2"}, { {1, 2}, {3, 4} } ),
AddColumn = Table.AddColumn(Source, "RowTotal", each [Col1] + [Col2])
in
AddColumn
Tại sao lại đặt tên each ? Theo dự đoán của tôi là tên xuất phát từ thực tế là each thường được sử dụng để đơn giản hóa định nghĩa hàm trong đó hàm sẽ được gọi một lần cho mỗi mục trong tập dữ liệu đầu vào (ví dụ: Table.AddColumn gọi một hàm đối số một lần cho mỗi hàng trong bảng ). Bất kể từ nguyên của nó là gì, each có thể được sử dụng bất cứ lúc nào bạn muốn xác định một hàm đối số, cho dù nó có được gọi một lần cho mỗi mục hay không.
Hàm trả về ( Returning Functions )
Các hàm cũng có thể trả về các hàm.
() => (a, b) => a * b
Những điều trên có vô nghĩa đối với bạn không ? Việc trả về các hàm trở nên thuận lợi hơn nhiều khi chúng ta tận dụng một thứ gọi là ( ) “closure”. Cho phép một hàm nhớ các giá trị của các biến trong phạm vi khi nó được xác định.
Dưới đây, khi chúng ta gọi hàm bên ngoài và truyền cho nó một giá trị cho x, hàm bên trong được trả về sẽ ghi nhớ giá trị của x. Khi chúng ta gọi hàm bên trong, chúng ta chỉ cần chuyển cho nó một giá trị cho y. Sau đó, nó sẽ nhân giá trị x đã nhớ với y.
(x) => (y) => (x * y)
Theo ví dụ ở trên, truyền 24 làm giá trị cho x trả về một hàm hoạt động có thể được định nghĩa như sau (lưu ý: cách hàm nhớ giá trị của x được sử dụng khi được tạo):
(y) =>
let
x = 24
in
(x * y)
Hàm đệ quy
Đôi khi, bạn có thể muốn một hàm gọi chính nó. Để một hàm tham chiếu đến tên của chính nó từ bên trong hàm, chỉ cần đặt tiền tố tham chiếu bằng ký tự “@”:
let
TongLienTiep = (x) => if x <= 0 then 0 else x + @TongLienTiep(x - 1),
KQ = TongLienTiep(24)
in
KQ