5.1 – Trả nhiều kết quả


Một tính năng độc đáo nhưng khá tiện lợi của Lua là các hàm có thể trả về nhiều kết quả. Một số hàm được xác định trước trong Lua trả về nhiều giá trị. Một ví dụ là hàm string.find, định vị một mẫu trong một chuỗi. Nó trả về hai chỉ số: chỉ số của ký tự nơi bắt đầu khớp mẫu và chỉ số nơi nó kết thúc (hoặc nil nếu nó không thể tìm thấy mẫu). Một phép gán nhiều lần cho phép chương trình nhận được cả hai kết quả:

    s, e = string.find("hello Lua users", "Lua")
    
    print(s, e)   -->  7      9

Các hàm được viết bằng Lua cũng có thể trả về nhiều kết quả, bằng cách liệt kê tất cả chúng sau từ khóa return. Ví dụ: một hàm để tìm phần tử lớn nhất trong một mảng có thể trả về cả giá trị lớn nhất và vị trí của nó:

function maximum (a)
      local mi = 1          -- maximum index
      local m = a[mi]       -- maximum value
      for i,val in ipairs(a) do
        if val > m then
          mi = i
          m = val
        end
      end
      return m, mi
    end
    
    print(maximum({8,10,23,12,5}))     --> 23   3

Lua luôn điều chỉnh số lượng kết quả từ một chức năng theo các trường hợp của cuộc gọi. Khi chúng ta gọi một hàm dưới dạng một câu lệnh, Lua sẽ loại bỏ tất cả các kết quả của nó. Khi chúng ta sử dụng một cuộc gọi như một biểu thức, Lua chỉ giữ lại kết quả đầu tiên. Chúng tôi chỉ nhận được tất cả các kết quả khi lệnh gọi là biểu thức cuối cùng (hoặc duy nhất) trong danh sách các biểu thức. Các danh sách này xuất hiện trong bốn cấu trúc trong Lua: nhiều phép gán, đối số cho lời gọi hàm, cấu trúc bảng và câu lệnh trả về. Để minh họa tất cả những cách sử dụng này, chúng tôi sẽ giả định các định nghĩa sau cho các ví dụ tiếp theo:

    function foo0 () end                  -- returns no results
    function foo1 () return 'a' end       -- returns 1 result
    function foo2 () return 'a','b' end   -- returns 2 results

Trong một phép gán nhiều, một lệnh gọi hàm dưới dạng biểu thức cuối cùng (hoặc duy nhất) tạo ra nhiều kết quả nếu cần để khớp với các biến:

    x,y = foo2()        -- x='a', y='b'
    x = foo2()          -- x='a', 'b' is discarded
    x,y,z = 10,foo2()   -- x=10, y='a', z='b'

Nếu một hàm không có kết quả hoặc không có nhiều kết quả như chúng ta cần, Lua tạo ra nils:

    x,y = foo0()      -- x=nil, y=nil
    x,y = foo1()      -- x='a', y=nil
    x,y,z = foo2()    -- x='a', y='b', z=nil

Một lệnh gọi hàm không phải là phần tử cuối cùng trong danh sách luôn tạo ra một kết quả:

    x,y = foo2(), 20      -- x='a', y=20
    x,y = foo0(), 20, 30  -- x=nil, y=20, 30 is discarded(bị loại bỏ)

Khi một lệnh gọi hàm là đối số cuối cùng (hoặc duy nhất) đối với một lệnh gọi khác, tất cả các kết quả từ lệnh gọi đầu tiên sẽ trở thành đối số. Chúng tôi đã thấy các ví dụ về cấu trúc này, với bản in:

    print(foo0())          -->
    print(foo1())          -->  a
    print(foo2())          -->  a   b
    print(foo2(), 1)       -->  a   1
    print(foo2() .. "x")   -->  ax         (see below)

Khi lệnh gọi foo2 xuất hiện bên trong một biểu thức, Lua sẽ điều chỉnh số lượng kết quả thành một; vì vậy, ở dòng cuối cùng, chỉ có “a” được sử dụng trong nối.

Hàm in có thể nhận được một số đối số thay đổi. (Trong phần tiếp theo, chúng ta sẽ xem cách viết các hàm với số đối số thay đổi.) Nếu chúng ta viết f (g ()) và f có số đối số cố định, Lua sẽ điều chỉnh số kết quả của g thành số tham số của f, như chúng ta đã thấy trước đây.

Một hàm tạo cũng thu thập tất cả các kết quả từ một cuộc gọi mà không cần bất kỳ điều chỉnh nào:

    a = {foo0()}         -- a = {}  (an empty table)
    a = {foo1()}         -- a = {'a'}
    a = {foo2()}         -- a = {'a', 'b'}

Như mọi khi, hành vi này chỉ xảy ra khi cuộc gọi là cuộc gọi cuối cùng trong danh sách; nếu không, bất kỳ lệnh gọi nào cũng tạo ra chính xác một kết quả:

a = {foo0(), foo2(), 4}   -- a[1] = nil, a[2] = 'a', a[3] = 4

Cuối cùng, một câu lệnh như return f () trả về tất cả các giá trị được trả về bởi f:

function foo (i)
      if i == 0 then return foo0()
      elseif i == 1 then return foo1()
      elseif i == 2 then return foo2()
      end
    end
    
    print(foo(1))     --> a
    print(foo(2))     --> a  b
    print(foo(0))     -- (no results)
    print(foo(3))     -- (no results)

Bạn có thể buộc một cuộc gọi trả về chính xác một kết quả bằng cách đặt nó trong một cặp dấu ngoặc đơn bổ sung:

    print((foo0()))        --> nil
    print((foo1()))        --> a
    print((foo2()))        --> a

Hãy lưu ý rằng một câu lệnh trả về không cần dấu ngoặc đơn xung quanh giá trị được trả về, vì vậy bất kỳ cặp dấu ngoặc nào được đặt ở đó đều được tính là một cặp bổ sung. Nghĩa là, một câu lệnh như return (f ()) luôn trả về một giá trị duy nhất, bất kể có bao nhiêu giá trị f trả về. Có thể đây là điều bạn muốn, có thể không.

Một chức năng đặc biệt với nhiều lần trả về là giải nén. Nó nhận một mảng và trả về kết quả là tất cả các phần tử từ mảng, bắt đầu từ chỉ mục 1:

    print(unpack{10,20,30})    --> 10   20   30
    a,b = unpack{10,20,30}     -- a=10, b=20, 30 is discarded

Một công dụng quan trọng để giải nén là trong cơ chế gọi chung. Cơ chế gọi chung cho phép bạn gọi bất kỳ hàm nào, với bất kỳ đối số nào, một cách động. Ví dụ, trong ANSI C, không có cách nào để làm điều đó. Bạn có thể khai báo một hàm nhận một số đối số thay đổi (với stdarg.h) và bạn có thể gọi một hàm biến, sử dụng con trỏ đến các hàm. Tuy nhiên, bạn không thể gọi một hàm với số lượng đối số thay đổi: Mỗi lệnh gọi bạn viết trong C có một số đối số cố định và mỗi đối số có một kiểu cố định. Trong Lua, nếu bạn muốn gọi một hàm biến f với các đối số là biến trong mảng a, bạn chỉ cần viết

f(unpack(a))

Lời gọi giải nén trả về tất cả các giá trị trong a, các giá trị này trở thành đối số của f. Ví dụ, nếu chúng tôi thực hiện

    f = string.find
    a = {"hello", "ll"}

thì lệnh gọi f (unpack (a)) trả về 3 và 4, giống hệt như lệnh gọi tĩnh string.find (“hello”, “ll”).

Mặc dù giải nén được xác định trước được viết bằng C, chúng ta cũng có thể viết nó bằng Lua, sử dụng đệ quy:

function unpack (t, i)
      i = i or 1
      if t[i] ~= nil then
        return t[i], unpack(t, i + 1)
      end
    end

Lần đầu tiên chúng ta gọi nó, với một đối số duy nhất, tôi nhận được 1. Sau đó, hàm trả về t [1] theo sau là tất cả các kết quả từ giải nén (t, 2), lần lượt trả về t [2] theo sau là tất cả các kết quả từ giải nén (t, 3), v.v., cho đến phần tử khác không cuối cùng.

Leave a comment