Bổ Sung Mô Tả Kiểu Tham Số Cho Lua - nói dối e blog

Bổ Sung Mô Tả Kiểu Tham Số Cho Lua

Hệ thống hàm trong Lua vốn không chứa thông tin kiểu dữ liệu của tham số. Trong thiết kế mô-đun đa ngôn ngữ, thông tin này lại đóng vai trò then chốt. Bởi khi gọi phương thức qua ranh giới ngôn ngữ, thao tác đóng gói dữ liệu (Marshaling) gần như không thể thực hiện nếu thiếu thông tin kiểu. Yêu cầu tương tự cũng rất quan trọng trong giao tiếp RPC.

Nhờ thiết kế bảng meta (metatable) tinh gọn của Lua, chúng ta hoàn toàn có thể bổ sung thông tin kiểu một cách tự nhiên mà không làm tổn hại hiệu năng. Hãy cùng phân tích phương pháp triển khai cụ thể.

Ôn lại kỹ thuật tạo lớp kiểu C++ trong Lua

Theo cách trình bày trong Programming in Lua (Pil), thông thường chúng ta sẽ chứa danh sách phương thức (tương đương bảng vtable trong C++) trong một table, rồi dùng table này làm giá trị cho thuộc tính __index của metatable. Ví dụ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
foo = {}
function foo:foobar(n)
  print(self)
  return n*n
end

foo = {__index = foo}
function create(class, obj)
  return setmetatable(obj or {}, class)    
end

t = create(foo)    -- Tạo đối tượng foo
t:foobar(100)      -- Gọi phương thức foobar

Người viết cũng từng phát triển các giải pháp “sành điệu” hơn theo phong cách OO hay C++. Tuy nhiên cần lưu ý: Lua không phải C++. Việc theo đuổi phong cách lập trình OO tuyệt đối có thể làm ta sa lầy vào kỹ thuật thay vì tập trung giải quyết vấn đề.

Kỹ thuật môi trường độc lập (setfenv)

Một phương pháp ấn tượng khác là sử dụng setfenv để tạo môi trường thực thi cô lập cho hàm:

1
2
3
4
5
6
7
8
9
local foo = {}
setfenv(function()
  function foobar(self, n)
    print(self)
    return n*n
  end
end, foo)()

for _,v in pairs(foo) do setfenv(v, _G) end

Kỹ thuật này không chỉ giúp tổ chức mã linh hoạt hơn, mà còn có thể mô phỏng hiệu ứng tương tự mệnh đề “with” trong Pascal.

Mô tả kiểu tham số trong khai báo hàm

Giờ hãy tập trung vào trọng tâm vấn đề: làm thế nào để thêm thông tin kiểu vào định nghĩa hàm? Cấu trúc mã sẽ như sau:

1
2
3
4
5
6
7
setfenv(function()
  def.foobar(table, number,
  function (self, n)
    print(self)
    return n*n
  end)
end, foo)()

Trong đó:

  • def.foobar là cú pháp đánh dấu khai báo đặc biệt
  • Dòng đầu chỉ định kiểu tham số: table, number
  • Dòng sau mới chứa tên tham số thực tế: self, n

Các từ khóa kiểu table, number, string… được định nghĩa sẵn trong môi trường, đồng thời áp dụng cơ chế __index của metatable để bắt các tên phương thức chưa khai báo. Điều này cho phép:

  1. Tự động ghi nhận thông tin kiểu
  2. Tạo lớp bọc kiểm tra kiểu (khi cần)
  3. Triển khai cơ chế thỏa thuận gọi hàm (theo kiểu Eiffel)

Triển khai đầy đủ với ví dụ minh họa

Dưới đây là phiên bản ví dụ hoàn chỉnh, bao gồm cả việc ghi nhận tên tham số:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function test()
  def.foobar(table.self, number.x, number.y, string.name, table.arg,
  function(self, x, y, name, arg)
    print(self)
    return x + y
  end)
end

local meta_arg_tostring = {
  __tostring = function(t) return t[1].." "..t[2] end
}

local function make_type(typename)
  return setmetatable({typename}, {
    __tostring = function(t) return typename end,
    __index = function(t, k) 
      return setmetatable({typename, k}, meta_arg_tostring) 
    end
  })
end

local meta_type = {__index = function(t, k) return k end}
local const_table = function(t, k, v) error "const table" end

local type_gen = setmetatable({
  number = make_type "number",
  boolean = make_type "boolean",
  string = make_type "string",
  table = make_type "table",
  def = setmetatable({}, {
    __index = function(t, k)
      return function(...)
        local vtbl = getfenv(3)
        vtbl.__meta[k] = arg
        vtbl[k] = setfenv(arg[#arg], _G)
        print(k, unpack(arg))  -- In thông tin nguyên mẫu hàm
      end
    end,
    __newindex = const_table
  })
}, {__index = _G, __newindex = const_table})

local class_vtbl = setmetatable({}, {__index = function(t, k)
  local ret = {__meta = {}}
  local gen = setfenv(k, type_gen)
  setfenv(function() gen() end, ret)()
  ret = {__index = ret}
  t[k] = ret
  return ret
end})

function create(class, obj)
  return setmetatable(obj or {}, class_vtbl[class])
end

local obj = create(test)
print(obj:foobar(1, 2))

Kết quả chạy thử:

1
2
3
foobar table self   number x    number y    string name   table arg    function: 003E7C38
table: 003ED0D8
3

Mở rộng khả năng

Khi nắm vững nguyên lý, hệ thống có thể được phát triển thêm:

  1. Mô phỏng enum:
1
enum { "one", "two", "three" }
  1. Khai báo kiểu trả về:
1
def(number, number).foobar(table.arg1, string.arg2)
  1. Chỉ định hướng tham số (theo chuẩn COM):
1
def.foobar(number.in_out.x, string.out.msg)

Giải pháp này không chỉ giúp tăng tính tự mô tả của mã nguồn mà còn tạo nền tảng cho các công cụ phân tích tự động, hỗ trợ documentation và kiểm tra lỗi ở tầng dịch vụ cao.

0%