Ngôn ngữ lập trình Lua: những hỏi-đáp bên lề

Tài liệu được dịch từ Lua Unofficial FAQ ( uFAQ ) http://www.luafaq.org/
Lua [ http://www.lua.org/ ] là một ngôn ngữ rất nhỏ gọn. Có thể còn ít bạn đọc biết đến Lua, nhưng nếu có thời hạn, bạn nên khám phá xem sao. Lua được dùng rất nhiều cho việc scripting ( viết những đoạn chương trình ngắn ), đặc biệt quan trọng là những đoạn plug-in do những người sử dụng viết thêm để lan rộng ra cho trình ứng dụng lớn có sẵn. Một ví dụ là game show World of Warcraft ( WoW ), người chơi hoàn toàn có thể kiểm soát và điều chỉnh độ mạnh yếu của những nhân vật, hay sửa đổi 1 số ít đặc thù game show bằng những đoạn lệnh Lua, thật mê hoặc !
Bài hỏi đáp này hoàn toàn có thể không tương thích với bạn đọc mới lập trình, nhưng nếu bạn đã biết qua ngôn ngữ Lua hay đã từng lập trình quen với C hay Python hoặc những ngôn ngữ tương tự như, thì đây là bài đọc rất mê hoặc và hé lộ nhiều đặc thù hay của ngôn ngữ Lua .

Bài hướng dẫn này được chia làm hai phần: phần 1 ở đây còn phần 2 ở trang này.

uFAQ

1 Ngôn ngữ
1.1 Phải mở màn từ đâu ?
1.2 Liệu Lua có tương thích trong vai trò ngôn ngữ thứ nhất ?
1.3 Liệu Lua có tương thích trong vai trò ngôn ngữ thứ hai ?
1.4 Có trình chỉnh sửa và biên tập và gỡ lỗi nào tốt không ?
1.5 Lua có vẻ như dài dòng. Tại sao nó không như C ?
1.5.1 Tại sao mảng trong Lua lại được đếm từ số 1 ?
1.5.2 Tôi hoàn toàn có thể dùng một biểu thức kiểu như “ x ? y : b ” của C không ?
1.6 Làm thế nào để bắt những truy vấn biến không được định nghĩa trong Lua ?
1.7 Lua có tương hỗ Unicode không ?
1.8 Có mẹo gì về tối ưu hóa không ?
1.9 Khi nào cần phải lo ngại về bộ nhớ ?
1.10 Khác biệt giữa pairs và ipairs là gì ?
1.11 Tại sao cần có một toán tử riêng để nối chuỗi ?
1.12 Tôi đã thấy có mã lệnh Lua gọi những hàm như strfind ; nhưng sao thử không được ?
1.13 Tại sao lệnh ‘ for k, v in t do ’ lại không hoạt động giải trí được nữa ?
1.14 Làm thế nào để đưa giá trị nil vào trong một mảng ?
1.15 Tôi hoàn toàn có thể xuất một bảng thế nào ?
1.16 Tại sao lệnh print ‘ hello ’ chạy được, còn print 42 thì không ?
1.17 a. f ( x ) và a : f ( x ) khác gì nhau ?
1.18 Các biến được lao lý khoanh vùng phạm vi thế nào ?
1.18.1 Tại sao những biến không để mặc định là biến địa phương ?
1.19 require và dofile khác gì nhau ?
1.20 Làm thế nào để nạp module nhị phân một cách minh bạch ?
1.21 Môi trường hàm là gì ?
1.22 Các hàm và toán tử có trùng tải được không ?
1.23 Làm thế nào để khiến hàm hoàn toàn có thể nhận số lượng những tham số không ít tùy ý ?
1.24 Làm thế nào để hàm trả lại nhiều giá trị ?
1.25 Bạn hoàn toàn có thể truyền những tham biến được đặt tên vào trong một hàm không ?
1.26 Tại sao không có lệnh continue ?
1.27 Có chính sách giải quyết và xử lý biệt lệ không ?
1.28 Không có lớp à ? Thế sao hoàn toàn có thể lập trình được ?
1.28.1 Closure là cái gì ?
1.29 Có nội suy chuỗi không, kiểu như “ $ VAR is expanded ” ?
1.30 Các lời gọi đuôi được tối ưu dùng làm gì được ?
1.31 Đóng gói thành một ứng dụng độc lập thế nào ?
1.32 Làm thế nào tôi hoàn toàn có thể nạp và chạy mã lệnh Lua ( vốn nhiều năng lực không đáng đáng tin cậy ) ?
1.33 Nhúng Lua vào trong một ứng dụng thế nào ?
1.34 Soạn tài liệu cho mã lệnh Lua thế nào ?
1.35 Cách kiểm tra kiểu đối số cho hàm ?
1.36 Làm thế nào để kết thúc thực thi một cách êm thấm ?
1.37 module ( ) hoạt động giải trí thế nào ?
1.37.1 Lời chỉ trích về module ( )
1.37.2 Mọi thứ sau module ( ) ?
1.38 Các bảng yếu là gì ?
1.39 Khác biệt giữa những cách kí hiệu chuỗi ?
1.40 Các yếu tố về thích hợp giữa Windows và Unix ?

Phụ trách: Steve Donovan (steve j donovan at gmail com), 2009-2011; phiên bản 2.0.

Giấy phép Creative Commons ; Nội dung hỏi đáp này hoàn toàn có thể sao chép chỉnh sửa và biên tập lại dưới bất kỳ hình thức nào, miễn là ghi công trạng của người đảm nhiệm .

Tóm Tắt

1 Ngôn ngữ

1.1 Phải bắt đầu từ đâu?

Hỏi đáp lua.org chính thức có ở đây, vốn là nơi tìm kiếm thông thiết yếu như những bản Lua có sẵn và những yếu tố giấy phép .
Cũng có một hỏi đáp Lua Wiki chưa không thiếu, thế cho nên list hỏi đáp bên lề này nhằm mục đích bổ trợ những thông tin còn thiếu và vấn đáp càng nhiều câu hỏi càng tốt một cách có ích .
Lua là một ngôn ngữ động, tân tiến với cú pháp tiện lợi :

function sqr(x)
    return x*x
end

local t = {}
for i = 1,10 do
    t[i] = sqr(i)
    if i == 10 then
        print('finished '..i)
    end
end

Dù có dáng vẻ tựa Basic, song Lua lại giống JavaScript hơn; như không có cơ chế lớp hẳn hoi và cách viết tương đương giữa a['x'] với a.x. Chỉ có rất ít kiểu dữ liệu: chuỗi, số, bảng, hàm, luồng (thread) và dữ liệu người dùng (userdata). Sự đơn giản này giúp Lua nhanh và gọn gàng hơn so với những ngôn ngữ khác có công năng tương đương.

Bản Lua User’s Wiki có nhiều ví dụ có ích và bàn luận thâm thúy, đây là chỗ thích hợp để khởi đầu học Lua .
Cuốn sách giáo khoa Programming in Lua được viết bởi Roberto Ierusalimschy, vốn là một trong những người lập nên Lua. ( Ấn bản thứ nhất của cuốn này có trên mạng. )
Nếu bạn đến với Lua từ một ngôn ngữ lập trình khác, hãy xem list những lỗi thường gặp .
Và ( tất yếu ), đọc tài liệu hướng dẫn, mặc dù rằng có lẽ rằng đây không phải là điểm khởi đầu thích hợp .

1.2 Có thích hợp làm ngôn ngữ lập trình thứ nhất không?

Lua là một ngôn ngữ ngăn nắp với cú pháp tiện lợi, sáng sủa vốn rất dễ đọc. Điều này khiến cho Lua là lựa chọn số một trong vai trò ngôn ngữ văn lệnh nhúng trong những trình ứng dụng lớn hơn, tuy nhiên cũng tương thích để ra mắt những khái niệm lập trình .
Các hàm đều là những giá trị hạng nhất và cũng hoàn toàn có thể ở dạng ẩn danh, nhờ đó người học hoàn toàn có thể triển khai phong thái lập trình hàm. Có tương hỗ closure nghiêm chỉnh và đệ quy gọi đuôi .
Lua được chạy dưới chính sách tương tác, nhờ vậy thuận tiện mày mò ngôn ngữ và những thư viện của nó .
Lua Wiki cũng so sánh với những ngôn ngữ khác .

1.3 Có thích hợp làm ngôn ngữ lập trình thứ hai không?

Một trường hợp là bạn đã có những ứng dụng C / C + + / Java / C # ( v.v. ) lớn và bạn muốn được cho phép người dùng viết mã lệnh tùy chỉnh những ứng dụng này. Lua sẽ chỉ làm tăng thêm 150 – 170K, cho bạn một ngôn ngữ lan rộng ra văn minh mà hiểu được những nhu yếu riêng ( như sandbox ) và hoàn toàn có thể thuận tiện tích hợp với mã lệnh sẵn có .
Trường hợp khác là bạn có rất nhiều thành phần dùng trong chuỗi việc làm và bạn cần tìm một ngôn ngữ kết nối để thuận tiện buộc chúng lại với nhau thật nhanh mà không phải qua quy trình chỉnh sửa và biên tập lại đầy phiền phức. ( Xem khái niệm xung đột được Ousterhout yêu cầu. )
Một trường hợp thực dụng khác nữa tích hợp cả hai trường hợp trên, đó là nhúng Lua để giúp gỡ lỗi. Hoàn toàn hoàn toàn có thể cung ứng một trình gỡ lỗi dòng lệnh mưu trí ( ngay cả cho những ứng dụng giao diện như Java / Swing ) chạy bằng Lua, vốn được cho phép bạn thao tác bên trong một ứng dụng, truy vấn trạng thái và chạy những mã lệnh kiểm thử nhỏ .

1.4 Có trình biên tập và gỡ lỗi nào tốt?

Xem LuaEditorSupport .
Những trình chỉnh sửa và biên tập và gỡ lỗi nào thông dụng đều được, gồm cả XCode .
SciTE là một trình chỉnh sửa và biên tập thông dụng trong Windows và nền GTK +, ứng dụng này tương hỗ Lua rất tốt. Phiên bản kèm trong Lua for Windows còn hoàn toàn có thể gỡ lỗi được mã lệnh Lua. Ngoài ra, trình này còn được lập trình được bằng trình thông dịch Lua nhúng trong đó .
Có một Lua plugin cho IntelliJ IDEA .

1.5 Lua có vẻ rất dài dòng. Tại sao nó không giống như C?

Độ ngăn nắp của C ( và môi trường tự nhiên Unix mà ngôn ngữ đó tăng trưởng ) bắt nguồn từ hạn chế về kĩ thuật của những máy teleprinter cổ lỗ sĩ. Bây giờ ta có cách điền lệnh bằng phím TAB cùng những trình chỉnh sửa và biên tập mưu trí được cho phép gõ tắt, thì viết mã lệnh Lua chẳng lâu hơn là bao so với C. Chẳng hạn, trong SciTE ta hoàn toàn có thể thêm đoạn lệnh sau vào trong viết tắt :

if = if | then \n \
    \n \
end

Sau đó mỗi khi gõ ‘if’ và ấn ctrl-B máy sẽ tự chèn đoạn lệnh khai triển, được thụt đầu dòng đúng quy cách.

Một điều nữa là ta dành nhiều thời hạn để đọc mã lệnh hơn là viết mã lệnh, thế cho nên yếu tố là mã lệnh này đọc có hay không. C rất dễ đọc với ai đã quen đọc C, tuy nhiên Lua dễ đọc hơn so với người lập trình tay ngang .
Một phiên bản Lua tựa C là Squirrel .
Có một yếu tố giúp Lua được coi là C của những ngôn ngữ kiểu động ; đó là Lua nhỏ, nhanh, và chỉ đi theo những công cụ thiết yếu nhất. Đây không chỉ là điểm tương đương, vì bản hiện thực Lua chỉ dùng những gì có trong thư viện C89 .

1.5.1 Tại sao các mảng trong Lua bắt đầu đếm từ số 1?

Thật kì quặc, điều khiến một số ít người quá khích là việc trong Lua, những mảng có chỉ số đếm từ một .
Theo kí hiệu toán học, chỉ số khởi đầu từ 1, và Lua cũng vậy. Nói cách khác, chỉ số không phải là offset .
Lý do lịch sửa ( xem sự tăng trưởng của Lua ) đó là Lua tăng trưởng để xử lý nhu yếu kĩ thuật tại công ty dầu hỏa vương quốc Brazil ( Petrobras ). Ngôn ngữ được phong cách thiết kế cho những kĩ sư vốn không phải lập trình viên chuyên nghiệp, và có năng lực là họ quen dùng FORTRAN hơn .

Bạn có thể khiến cho các mảng đếm từ không, song các hàm thư viện chuẩn (như table.concat) sẽ không nhận ra phần tử số 0. Và # sẽ luôn trả lại kích thước bảng trừ đi một.

t = {[0]=0,10,20,30}  -- constructor mảng hơi lủng củng
for i = 0,#t do print(i,t[i]) end  -- không phải 0,#t-1 !

1.5.2 Liệu tôi có thể dùng một biểu thức kiểu như “x ? y : b” của C?

Biểu thức viết trong Lua là res = x and y or b  vốn hoạt động được vì giá trị của những phép toán này không phải đơn thuần là giá trị boole mà là toán hạng thứ hai. Sẽ có sự lượng giá biểu thức “ngắn mạch”, theo nghĩa biểu thức b sẽ không bị lượng giá khi điều kiện là đúng.

Tuy nhiên lưu ý trường hợp khi ynil hoặc false. Cái and thứ nhất bị thất bại và dù sao ta cũng sẽ chọn lấy b.

Lua cũng hiểu nhiều giá trị :

> print (false and 5 or 10,'hello')
10      hello

Người ta sẽ thường viết một hàm phụ trợ :

function choose(cond,a,b)
    if cond then return a else return b end
end

Cách này định giá cả hai giá trị ngay từ đầu, thế cho nên nó chỉ tương thích với những giá trị đơn thuần, tuy nhiên lại rất bảo đảm an toàn .

1.6 Làm thế nào để can thiệp việc truy cập những biến chưa được định nghĩa trong Lua?

Mặc định là một biến chưa biết sẽ được tra trong bảng toàn cục hoặc bảng modul và giá trị của nó sẽ là nil. Đây là một giá trị hợp lệ cho biến, bởi vậy việc gõ nhầm tên biến sẽ có kết quả rất hay, ngay cả khi ta tránh dùng những biến toàn cục.

Có một số cách tiếp cận để áp đặt sự chặt chẽ đối với biến toàn cục. Cách dễ nhất là kiểm tra một cách động xem một biến toàn cục đã cho được khởi tạo minh bạch hay chưa; nghĩa là liệu nó có được đặt bằng nil ở đâu đó không (xem strict.lua trong thư mục etc của bản phân phối Lua bạn dùng.)

File tests/globals.lua trong bản phân phối lại có cách tiếp cận khác, đó là truyền mã bytecode của trình biên dịch luac qua grep, tìm những truy cập toàn cục; song điều này chỉ giúp ích nếu bạn ‘tránh biến toàn cục như tránh hủi’. Nói riêng, có những hàm toàn cục và bảng toàn cục cung cấp bởi thư viện chuẩn.

lua-checker là một công cụ kiểm tra mã lệnh tựa lint.

LuaInspect được cho phép nghiên cứu và phân tích mã lệnh Lua theo thời hạn thực bằng [ SciTE ] ( ( http://www.scintilla.org/SciTE.html ) và một plugin vim ; cũng tương hỗ hiệu quả đầu ra tĩnh như HTML. Điều này được cho phép tô màu mã lệnh theo ngữ nghĩa cho Lua, để những biến địa phương được ghi lại và hoàn toàn có thể truy vấn được, còn những biến toàn cục chưa biết được dán nhãn màu đỏ .

1.7 Lua có hỗ trợ Unicode không?

Có và không. Các chuỗi lua Lua hoàn toàn có thể chứa kí tự bất kể ( chuỗi không kết thúc bởi NUL ) vì thế chuỗi Unicode hoàn toàn có thể được truyền quanh mà không gặp yếu tố gì, cũng như tài liệu nhị phân. Tuy nhiên, những thư viện chuỗi sẵn có đều xét những kí tự 1 – byte, nên cần phải có thư viện đặc biệt quan trọng ; xem slnunicode hay ICU4Lua .

1.8 Những mẹo nào về tối ưu hóa?

Câu hỏi tiên phong là, bạn thực sự gặp yếu tố chưa ? Có phải chương trình chưa đủ nhanh ? Hãy nhớ ba nhu yếu cơ bản về mạng lưới hệ thống : Đúng đắn, Mạnh mẽ và Hiệu quả, và quy tắc cơ bản trogn kĩ thuật là bạn chỉ hoàn toàn có thể chọn hai trong số 3 nhu yếu đó thôi .

Lời nói của Donald Knuth về tối ưu hóa thường được nhắc tới : “ Nếu bạn tối ưu hóa mọi thức, bạn sẽ luôn thấy xấu số ” và “ ta nên quên đi những chỗ kém hiệu suất cao nhỏ, ví dụ điển hình 97 % thời hạn : tối ưu hóa sớm là căn nguyên của mọi điều xấu. ”

Cứ coi một chương trình là đúng đắn và ( hy vọng là ) can đảm và mạnh mẽ. Có một cái giá nhất định trong việc tối ưu hóa chương trình này, cả so với thời hạn lập trình và mức độ dễ đọc mã lệnh. Nếu bạn không biết chỗ nào mã lệnh chậm, thì bạn sẽ tốn thời hạn làm cho cả chương trình xấu xí và hoàn toàn có thể chỉnh nhanh hơn chút ( thế cho nên mà ông Knuth nói rằng bạn sẽ thấy xấu số. ) Cho nên bước thứ nhất là dùng chương trình LuaProfiler để tìm những phần có hiệu suất cao kém, vốn sẽ là những hàm thường được gọi nhiều và những phép toán nằm sâu trong những vòng lặp bên trọng. Chỉ có đây là những phần mà việc tối ưu hóa sẽ đem lại hiệu quả, và thường chúng chỉ là phần nhỏ trong chương trình, nên tổng số bạn chỉ phải làm phần việc nhỏ, và sẽ thấy niềm hạnh phúc hơn và phạm ít điều xấu xa trong đống mã lệnh xấu .
Có 1 số ít mẹo riêng với Lua ở đây. Nếu máy bạn có cài LuaJIT, hãy sử dụng nó .
Trong Lua, sự tương tự với việc viết những vòng lặp trong bằng assembly, đó là ghi lại đoạn mã lệnh dùng CPU nhiều, viết nó bằng C, và dùng như một module lan rộng ra. Nếu làm đúng, bạn sẽ có một chương trình Lua với hiệu năng gần như bằng C, nhưng ngắn hơn và dễ bảo dưỡng hơn .
Với LuaJIT bạn hoàn toàn có thể không cần dùng C chút nào, đặc biệt quan trọng là vì foreign-function interface FFI có sẵn khiến cho việc truy vấn những hàm và cấu trúc C bên ngoài rất hiệu suất cao. Điều này cũng hoàn toàn có thể cho ta những cách màn biểu diễn mảng chứa kiểu tài liệu C với dung tích hiệu suất cao hơn, so với việc dùng những bảng .

1.9 Khi nào tôi cần phải lo về bộ nhớ?

Câu vấn đáp gọn là : chỉ khi nào bạn phải lo thực sự. Thường thì Lua làm điều đúng, nhưng nếu nó dùng quá nhiều bộ nhớ thì bạn phải hiểu đôi điều về cách quản trị bộ nhớ trong Lua .
Người ta hoàn toàn có thể lo ngại về việc có quá nhiều đối tượng người tiêu dùng chuỗi trong bộ nhớ. Nhưng Lua lại ghi chuỗi bên trong, thế cho nên mỗi chuỗi riêng chỉ có đủng một phiên bản duy nhất. Và mã lệnh sau đây dùng ít bộ nhớ hơn là bạn tưởng :

local t = {}
for i = 1,10000 do
    t[i] = {firstname = names[i], address = addr[i]}
end

Nó cũng giúp cho việc so sánh chuỗi bằng nhau thực thi rất nhanh .
Nối quá nhiều chuỗi hoàn toàn có thể sẽ chậm và kém hiệu suất cao. Sau đây là cách đọc sai lầm đáng tiếc nội dung của file vào một chuỗi :

local t = ""
for line in io.lines() do
  t = t .. line .. '\n'
end

Điều này tệ, và cũng tương tự như khi triển khai trong Java hoặc Python ; những chuỗi không hề biến hóa được, và việc làm vừa qua là liên tục tạo ra và hủy bỏ những chuỗi .

Nếu bạn cần phải nối rất nhiều chuỗi, hãy đặt nó vào trong một bảng rồi ghép lại bằng table.concat()

Lua, cũng như đa số ngôn ngữ lập trình hiện đại, có thu hồi bộ nhớ tự động. Bộ nhớ được huy động cho các bảng, v.v. đều được tự động thu lại sau khi dữ liệu không còn được tham chiếu đến nữa. Chẳng hạn ta có t = {1,2,3} rồi sau đó gán t = {10,20,30}; bảng ban đầu sẽ trở nên lẻ loi, và khi bộ thu hồi đến thì bảng đó sẽ bị đưa đi.

Vì vậy điều đầu tiên cần nhớ là bộ nhớ dữ liệu sẽ chỉ được giải phóng nếu bạn chính thức không tham chiếu đến dữ liệu đó. Và thứ hai, thường thì lúc thu hồi là lúc bạn không định trước; song bạn cũng có thể ra lệnh thu hồi ngay bằng collectgarbage('collect'). Một chi tiết ở đây là thường phải gọi hàm trên hai lần, vì có những hàm kết thúc (finalizer) sẽ được xếp lịch thực hiện khi gọi thu hồi lần thức nhất, và hàm kết thúc đó mới chính thức thực hiện ở lần gọi thứ hai.

1.10 pairs và ipairs khác gì nhau?

Các bảng và cấu trúc dữ liệu tổng quát ở Lua có thể được xem như ‘giống mảng’ và cũng ‘giống ánh xạ’. Ý tưởng là bạn có thể dùng chúng một cách hiệu quả với vai trò mảng co giãn được với những chỉ số nguyên, song cũng có thể đánh chỉ số chúng với bất kì giá trị khác như một ánh xạ hash. pairs(t) cung cấp một bộ lặp chung qua các khóa và giá trị trong một bảng, dù là số hay không, mà không theo thứ tự nào; còn ipairs(t) thì duyệt theo phần mảng theo đúng thứ tự.

Các bảng hoàn toàn có thể chứa hỗn hợp những thành phần mảng và ánh xạ :

t = {fred='one',[0]=1; 10,20,30,40}

Không cần có dấu chấm phẩy ở trên, song nó có thể được dùng làm một dấu ngăn cách thứ hai (ngoài dấu phẩy) trong lệnh tạo bảng. Chỉ vì 0 là một chỉ số không có là nó phải là chỉ số của mảng! Mà theo định nghĩa, chỉ số mảng phải từ 1 đến #t.

Hai lệnh sau là tương tự với mục tiêu truy vấn những thành phần của mảng :

for i,v in ipairs(t) do f(i,v) end

for i=1,#t do f(i,t[i]) end

Các bảng trong Lua màn biểu diễn mảng thưa một cách tự nhiên :

t = {[1] = 200, [2] = 300, [6] = 400, [13] = 500}

Trong trường hợp này, #t không đáng tin cậy, bởi toán tử độ dài # chỉ được định nghĩa cho các mảng không thưa (mảng không có vị trí rỗng). Nếu bạn muốn theo dõi kích thước của một mảng thưa, thì cách tốt nhất là giữ một biến đếm và tăng dần giá trị của nó:

t[idx] = val;
t.n = t.n + 1

Sẽ hơi phiền phức nếu chỉ muốn duyệt qua phần không phải mảng của một bảng :

local n = #t
for k,v in pairs(t) do
  if type(k) ~= 'number' or k < 1 or k > n then
    print(k,v)
  end
end

Tại sao không đặt ra một phép lặp cho việc này ? Lý do là những mảng thì có phần mảng và phần ánh xạ trong cụ thể về lập trình rồi, chứ không phải thuộc về tính năng nữa. ( Cso thể diễn đạt trực tiếp đoạn mã lệnh trên dưới dạng một phép lặp tự tạo. )

1.11 Tại sao cần một toán tử riêng cho phép nối chuỗi?

Việc có riêng toán tử .. sẽ hạn chế khả năng nhầm lẫn.

Phép trùng tải toán tử ‘+’ phục vụ nối chuỗi đã trở thành truyền thống lập trình. Nhưng nối chuỗi không phải là phép cộng, và sẽ có ích nếu ta phân biệt được hai khái niệm này. Trong Lua, các chuỗi có thể được đổi thành số lúc thích hợp (ví dụ 10+’20’) và số có thể đổi thành chuỗi (ví dụ 10 .. 'hello'). Việc có toán tử riêng thì đồng nghĩa với tránh nhầm lẫn, như đã xảy ra điển hình trong JavaScript.

Khi trùng tải toán tử, sự phân biệt này vẫn hữu ích. Chẳng hạn, một lớp List có thể được tạo ra trong đó .. một lần nữa có nghĩa là ghép nối, tức là nối các danh sách khác nhau để tạo nên danh sách dài hơn. Ta cũng có thể định nghĩa phép + với danh sách các số, khi đó phép toán này nghĩa là cộng từng phần tử, theo đó v+u là một danh sách chứa các tổng phần tử của v và u, nghĩa là chúng được coi như những vectors.

1.12 Tôi đã từng thấy mã lệnh Lua có viết các hàm như strfind; nhưng sao tôi thử không được?

Các hàm như strfind chỉ có trong mã lệnh Lua 4.0; bây giờ các hàm này nằm trong bảng, như string.find. Hãy tra mục thư viện chuẩn của tài liệu hướng dẫn.

Trong Lua 5.1, mọi chuỗi đều có một bảng meta chỉ đến bảng string, bởi vậy s:find('HELLO') tương đương với string.find(s,'HELLO').

1.13 Tại sao ‘for k,v in t do’ không còn hoạt động nữa?

Đây là một trong những thay đổi lớn từ phiên bản Lua 5.0 đến 5.1. Mặc dù gõ câu lệnh như vậy thì rất tiện tay, song cách viết không rõ ràng bằng việc nêu chính xác các cặp khóa – giá trị được phát sinh thế nào. Lệnh này tương đương với cách viết pairs(t), và vì vậy cách viết cũ không phù hợp với trường hợp chung khi cần lặp qua các phần tử của mảng theo thứ tự.

Trong câu lệnh for, t phải là một hàm. Ở trường hợp đơn giản nhất, nó phải là một hàm trả lại giá trị mỗi khi được gọi đến, và phải trả lại nil khi vòng lặp kết thúc.

Rất dễ viết các hàm lặp. Sau đây là một hàm lặp đơn giản qua tất cả các phần tử trong mảng, hàm lặp này hoạt động hệt như ipairs:

function iter(t)
  local i = 1
  return function()
     local val = t[i]
     if val == nil then return nil end
     local icurrent = i
     i = i + 1
     return icurrent,val
  end
end

for i,v in iter {10,20,30} do print(i,v) end

1.14 Cách đặt một giá trị nil vào trong một mảng thế nào?

Việc đặt nil trực tiếp vào một mảng sẽ gây ra một ‘lỗ hổng’ khiến cho toán tử độ dài bị nhầm lẫn. Có hai cách khắc phục điều này. Một cách là dùng mảng thưa với kích cỡ được nêu rõ. Khi đó, hàm lặp phải trả về cả chỉ số và giá trị:

local sa = {n=0}

function append(t,val)
   t.n = t.n + 1
   t[t.n] = val
end

function sparse_iter(t)
  local i,n = 1,t.n  -- ở đây phải viết cụ thể là t.n ! Còn #t sẽ không dùng được
  return function()
   local val = t[i]
   i = i + 1
   if i > n then return nil end
   return i,val
  end
end

append(sa,10)
append(sa,nil)
append(sa,20)

for i,v in sparse_iter(sa) do ... end

Cách khác là lưu trữ một giá trị độc nhất, đặc biệt là Sentinel thay cho nil thực thụ; chỉ cần viết Sentinel = {} là được. Các vấn đề này được bàn luận kĩ hơn trong wiki.

1.15 Làm thế nào để sổ nội dung của một bảng?

Đây là một trong những điều cần đến trợ giúp của thư viện “ hơi chính thức ”. Câu hỏi tiên phong là, bạn muốn sổ nội dung của bảng ra để gỡ lỗi, hay là viết nội dung những bảng lớn để sau này nạp lại chúng ?
Một giải pháp đơn thuần cho yếu tố thứ nhất :

function dump(o)
    if type(o) == 'table' then
        local s = '{ '
        for k,v in pairs(o) do
                if type(k) ~= 'number' then k = '"'..k..'"' end
                s = s .. '['..k..'] = ' .. dump(v) .. ','
        end
        return s .. '} '
    else
        return tostring(o)
    endend

Tuy vậy, cách này không hiệu suất cao lắm với những bảng lớn vì có những phép nối chuỗi trong đó, và sẽ gây rắc rối nếu bạn có những tham chiếu vòng tròn .
PiL hướng dẫn cách giải quyết và xử lý những bảng cả có lẫn không tham chiếu vòng tròn .
Những giải pháp khác cũng có ở đây .
Và nếu bạn muốn hiểu một tập hợp phức tạp gồm những bảng có liên hệ với nhau, thì cách vẽ sơ đồ sẽ là tốt nhất để bộc lộ mối liên hệ ; bài viết này cho một đoạn mã lệnh nhằm mục đích tạo ra hiển thị Graphviz về những mối liên hệ bảng thế này. Còn đây là một tác dụng ví dụ .

1.16 Tại sao print ‘hello’ lại thực hiện được, nhưng print 42 thì không?

Có sự nhầm lẫn này là vì một số ngôn ngữ (như Python 2.x) có câu lệnh print, còn print lại là một hàm trong Lua (hay trong Python 3).

Tuy  nhiên, có hai trường hợp mà bạn có thể bỏ cặp ngoặc đơn đi khi gọi hàm trong Lua function: bạn có thể truyền đúng một gía trị kiểu chuỗi hay kiểu bảng. Bởi vậy gọi một hàm như myfunc{a=1,b=2} là hoàn toàn được. Cách viết tiện dụng này có từ truyền thống của Lua là một ngôn ngữ mô tả dữ liệu.

1.17 a.f(x) và a:f(x) khác nhau thế nào?

a.f đơn giản nghĩa là ‘tra tìm f trong a’. Nếu kết qủa là dạng ‘gọi được’ thì bạn có thể gọi nó. Một cách làm mẫu thông dụng là lưu trữ các hàm trong một bảng; cách này hay được dùng trong những thư viện chuẩn, ví dụ table.insert, v.v.

a:f thực ra bản thân nó thì không có nghĩa cụ thể. a:f(x) là dạng viết tắt của a.f(a,x) – nghĩa là tra tìm hàm trong bối cảnh của đối tượng a, rồi gọi nó bằng việc truyền đối tượng a như tham biến đầu.

Cho trước một đối tượng a, nhiều khi người lập trình mắc lỗi gọi phương thức bằng dấu chấm. Đành rằng có phương thức như vậy, nhưng tham biến self lại không được đặt, và phương thức sẽ bị lỗi khi chạy, đưa ra thông báo rằng tham số thứ nhất không phải là đối tượng mà phương thức trông đợi.

1.18 Các biến được phân định tầm ảnh hưởng thế nào?

Theo mặc định, các biến có tầm ảnh hưởng toàn bộ (hay toàn cục), và chúng chỉ có ảnh hưởng cụ bộ nếu là các đối số của hàm hay được khai báo rõ là local. (Điều này trái ngược với quy tắc trong Python, ở đó bạn phải dùng từ khóa global.)

G = 'hello'  -- toàn bộ 

function test (x,y)  -- x,y chỉ cục bộ trong test
  local a = x .. G
  local b = y .. G
  print(a,b)
end

Các biến cục bộ được ‘ phân vùng theo ngữ vựng ’, và bạn hoàn toàn có thể khai báo bất kỳ biến nào bên trong một khối lệnh được lồng ghép mà không ảnh hưởng tác động đến những lệnh ngoài khối .

do
  local a = 1
  do
    local a = 2
    print(a)
  end
  print(a)
end

=>
2
1

Có một khả năng khác về tầm ảnh hưởng. Nếu một biến không phải là cục bộ, thì nói chung nó sẽ được đặt trong tầm ảnh hưởng của module đó,  vốn thường là trong bảng toàn bộ (_G nếu bạn muốn viết rõ ra) nhưng được định nghĩa lại bằng hàm module:

-- mod.lua (trong đường dẫn module của Lua)
module 'mod'

G = 1  -- bên trong module này

function fun(x) return x + G end --như trên

-- moduser.lua (ở bất kì đâu)
require 'mod'
print(mod.G)
print(mod.fun(41))

1.18.1 Tại sao các biến không mặc định là có tầm ảnh hưởng cục bộ?

Dĩ nhiên, có vẻ như sẽ rất dễ nếu ta chỉ phải khai báo rõ ràng những biến cục bộ trong toàn cảnh một đoạn mã lệnh địa phương. Câu vấn đáp ngắn gọn là : Lua không phải là Python, nhưng thực ra có những lí do hài hòa và hợp lý về việc những biến cục bộ với tầm ảnh hưởng tác động theo ngữ vựng phải được khai báo rõ ràng. Xem trang wiki .

1.19 require và dofile khác gì nhau?

requiredofile đều nạp và chạy các file Lua. Khác biệt thứ nhất là bạn truyền tên module đến require và một đường dẫn đến file cho dofile.

Như vậy require dùng đường dẫn tìm kiếm nào? Nó được chứa trong giá trị của package.path: trên một hệ thống Unix thì các đường dẫn này sẽ có dạng như (lưu ý các dấu chấm phẩy dùng để phân biệt từng đường dẫn):

$> lua -e "print(package.path)"
./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

Trong mạng lưới hệ thống Windows :

C:\>lua -e "print(package.path)"
.\?.lua;C:\Program Files\Lua\5.1\lua\?.lua;C:\Program Files\Lua\5.1\lua\?\init.
lua;C:\Program Files\Lua\5.1\?.lua;C:\Program Files\Lua\5.1\?\init.lua;C:\Progra
m Files\Lua\5.1\lua\?.luac

Cần chú ý quan tâm rằng bản thân Lua không biết gì về thư mục ; nó chỉ lấy tên một module rồi thay thế sửa chữa nó là ‘ ? ’ trong từng chuỗi mẫu và xem liệu file có sống sót không ; nếu có thì nạp file này .

Một thông báo lỗi rất cụ thể sẽ bật lên nếu bạn cố thử require một module mà không tồn tại. Điều này cho bạn thấy rõ ràng cách Lua đang tìm kiếm module đó bằng cách khớp các mục đường dẫn:

$> lua -lalice
lua: module 'alice' not found:
        no field package.preload['alice']
        no file './alice.lua'
        no file '/home/sdonovan/lua/lua/alice.lua'
        no file '/home/sdonovan/lua/lua/alice/init.lua'
        no file './alice.so'
        no file '/home/sdonovan/lua/clibs/alice.so'
        no file '/home/sdonovan/lua/clibs/loadall.so'

Điểm khác biệt thứ hai là require theo dõi những module nào đã được nạp, bởi vậy việc gọi lại chúng sẽ không khiến cho module bị nạp lại.

Điểm khác biệt thứ ba là require cũng sẽ nạp các module nhị phân. Theo cách tương tự, package.cpath được dùng để tìm các module – đó là các thư viện chia sẻ (hay DLL) vốn xuất các hàm khởi tạo.

$> lua -e "print(package.cpath)"
./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so

C:\>lua -e "print(package.cpath)"
.\?.dll;.\?51.dll;C:\Program Files\Lua\5.1\?.dll;C:\Program Files\Lua\5.1\?51.dl
l;C:\Program Files\Lua\5.1\clibs\?.dll;C:\Program Files\Lua\5.1\clibs\?51.dll;C:
\Program Files\Lua\5.1\loadall.dll;C:\Program Files\Lua\5.1\clibs\loadall.dll

package.pathpackage.cpath có thể thay đổi được, bằng cách này sẽ thay đổi biểu hiện của require. Lua cũng sẽ dùng các biến môi trường LUA_PATHLUA_CPATH nếu chúng được định nghĩa, để ghi đè lên các đường dẫn mặc định.

Thế ‘. / ?. lua ’ sẽ khớp với gì ? Chuỗi này khớp với bất kỳ module Lua nào trong thư mục hiện thời, vốn rất tiện lợi cho việc kiểm thử chương trình .

Tại sao lại có mẫu ‘/usr/local/share/lua/5.1/?/init.lua’? Cái này cho phép đặt các gói độc lập hoàn chỉnh chứa những module. Bởi vậy nếu có một thư mục ‘mylib’ trong đường dẫn Lua, thì require 'mylib' sẽ nạp ‘mylib/init.lua’.

Điều gì sẽ xảy ra nếu tên module có chứa dấu chấm, như require 'socket.core? Lua sẽ thay thế dấu chấm bằng các dấu phân cách thư mục phù hợp (như ‘socket/core’ hay ‘socket\core’) và thực hiện tìm kiếm theo dạng này. Bởi vậy trong hệ thống Unix, nó sẽ thay thế ‘?’ trong ‘/usr/local/lib/lua/5.1/?.so’ để cho ra ‘/usr/local/lib/lua/5.1/socket/core.so’ vốn khớp với mẫu đã cho.

1.20 Cách nạp module nhị phân một cách minh bạch?

Một module nhị phân là một thư viện chia sẻ trong đó phải có một điểm dẫn vào với tên gọi đặc biệt. Chẳng hạn, nếu tôi có một module nhị phân là fred thì hàm khởi tạo của nó phải có tên là luaopen_fred. Mã lệnh sau sẽ nạp fred một cách rõ ràng, với một đường dẫn đầy đủ cho trước:

local fn,err = package.loadlib('/home/sdonovan/lua/clibs/fred.so','luaopen_fred')
if not fn then print(err)
else
  fn()
end

(Trong Windows, hãy dùng phần mở rộng .dll)

Lưu ý rằng bất kì hàm nào được xuất bởi một thư viện chia sẻ thì đều có thể gọi được kiểu này, miễn là bạn biết tên nó. Khi viết các phần mở rộng trong C++, điều quan trọng là phải xuất điểm dẫn vào bằng cách viết extern "C", nếu không tên gọi sẽ bị hỏng. Trong Windows, bạn phải đảm bảo rằng ít nhất điểm dẫn vào này phải được xuốt qua lệnh __declspec(dllexport) hay dùng một file .def .

1.21 Các môi trường hàm là gì?

setfenv hoàn toàn có thể thiết lập môi trường tự nhiên cho một hàm. Thường thì môi trường tự nhiên của hàm là một bảng có tầm tác động ảnh hưởng hàng loạt, tuy nhiên điều này hoàn toàn có thể đổi khác được. Điều này rất có lợi cho sandbox, vì bạn hoàn toàn có thể trấn áp được toàn cảnh triển khai mã lệnh, và ngăn không cho mã lệnh đó triển khai điều xấu. Nó cũng được dùng để thực thi khoanh vùng phạm vi module .

function test ()
    return A + 2*B
end

t = { A = 10; B = 20 }

setfenv(test,t)

print(test())
=>
50

Các môi trường hàm bị gỡ bỏ từ phiên bản Lua 5.2 trở đi, song vẫn có những cách làm tương đương với tính năng không kém. Chẳng hạn, bạn muốn biên dịch mã lệnh trong một môi trường khởi đầu riêng; hàm load mở rộng có thể là mã lệnh được truyền, là một tên gọi mã lệnh, một kiểu chế độ (như “t” ở đây viết tắt cho text only – chỉ dạng chữ) và một bảng môi trường (có hoặc không đều được).  load biên dịch mã lệnh này và trả về một hàm function cần được lượng gía:

Lua 5.2.0 (beta)  Copyright (C) 1994-2011 Lua.org, PUC-Rio
> chunk = load("return x+y","tmp","t",{x=1,y=2})
> = chunk
function: 003D93D0
> = chunk()
3

Bạn hoàn toàn có thể dùng cách này để nạp những đoạn mã lệnh do người dùng viết trong một môi trường tự nhiên được trấn áp ; trường hợp này bảng ‘ toàn cục ’ chỉ chứa hai biến đã chỉ định .

Ví dụ về setfenv trong Lua 5.1 có thể được viết bằng biến đặc biệt _ENV trong Lua 5.2:

> function test(_ENV) return A + 2*B end
> t = {A=10; B=20}
> = test(t)
50

1.22 Các hàm và toán tử có thể được trùng tải (overload) không?

Hàm không hề trùng tải được, tối thiểu là không được lúc biên dịch .
Tuy nhiên, bạn hoàn toàn có thể ‘ trùng tải ’ dựa trên những kiểu của đối số mà bạn nhận được. Chẳng hạn, ta cần một hàm có thể thao tác được với một hay nhiều số lượng :

function overload(num)
  if type(num) == 'table' then
    for i,v in ipairs(num) do
      overload(v)
    end
  elseif type(num) == 'number' then
    dosomethingwith(num)
  end
end

Các toán tử thông dụng hoàn toàn có thể trùng tải được bằng những phương pháp meta ( metamethods ). Nếu một đối tượng người tiêu dùng ( một bảng Lua hay một userdata của C ) có một bảng meta thì ta hoàn toàn có thể tinh chỉnh và điều khiển được ý nghĩa của những toán tử số học ( như + – * / ^ ), phép nối chuỗi .., phép gọi hàm ( ) và toán tử so sánh = = ~ = < > .
Việc trùng tải ( ) được cho phép sống sót những ‘ đối tượng người tiêu dùng hàm ’ hay functor, vốn hoàn toàn có thể được dùng đến mỗi khi Lua cần một đối tượng người dùng nào đó gọi được .

Lưu ý một điểm hạn chế về các toán tử trùng tải như ==: cả hai đối số phải có cùng kiểu dữ liệu. Thật không may là bạn không thể tạo ra một lớp SuperNumber rồi gán sn == 0 được. Bạn phải tạo ra một thực thể của SuperNumber gọi là Zero và sử dụng nó vào trong việc so sánh.

Bạn có thể điều khiển ý nghĩa của a[i] nhưng đây không nói riêng về việc trùng tải toán tử []. __index được kích hoạt khi Lua không thể tìm được một khóa bên trong bảng. __index có thể được đặt là một bảng hay một hàm; các đối tượng thường được thực hiện bằng việc đặt  __index thành bản thân bảng meta đó, và bằng việc đặt các phương thức vào trong bảng meta này. Vì a.fun tương đương với a['fun'], không có cách nào để phân biệt giữa việc tra tìm bằng chỉ số rõ ràng và việc dùng dấu chấm. Một lớp Set nôm na sẽ đặt những phương thức nào đó vào trong bảng meta rồi lưu các phần tử trong tập hợp như những khóa trong bản thân đối tượng. Nhưng nếu Set có phương thức là ‘set’, thì s['set'] sẽ luôn đúng, mặc dù ‘set’ không phải là thành viên của set.

Các toán tử size và hành vi khi được gom rác bộ nhớ đều hoàn toàn có thể được ghi đè bằng những phần lan rộng ra C, chứ không hề chỉ bằng Lua 5.1. hạn chế này đã được dỡ bỏ trong Lua 5.2 .

1.23 Có cách nào để các hàm nhận số lượng biến đổi các đối số không?

Các hàm Lua rất “ dễ tính ” với những đối số dư thùa ; bạn hoàn toàn có thể lấy một hàm không đối số rồi truyền đối số cho nó ; và những đối số này đơn thuần là bị vứt bỏ .
Cú pháp của một hàm nhận số lượng đối số không định rõ ( ‘ hàm variadic ’ ) cũng giống như trong C :

function vararg1(arg1,...)
  local arg = {...}
  -- dùng arg như một bảng để truy cập các đối số
end

Vậy ... là gì?  Đó là cách viết tắt cho tất cả những đối số không đặt tên. Vậy function I(...) return ... end là một hàm ‘identity’ tổng quát, vốn nhận một số tùy ý các đối số rồi trả lại những gía trị này. Trong trường hợp đó, {...} sẽ được điền bởi những gía trị của tất cả đối số thêm.

Nhưng vararg1 có một vấn đề không nhỏ. nil chắc chắn là một gía trị phải bỏ qua, nhưng sẽ gây nên một lỗ hổng trong bảng arg, nhĩa là # sẽ trả lại gía trị sai.

Một cách làm chắc như đinh hơn là :

function vararg2(arg1,...)
  local arg = {n=select('#',...),...}
  -- arg.n is the real size
  for i = 1,arg.n do
    print(arg[i])
  end
end

Ở đây ta dùng hàm select để tìm ra số đối số thực sự được truyền vào. Đây là một dạng mẫu thông dụng với các vararg đối nỗi nó trở thành một hàm mới có tên gọi table.pack trong Lua 5.2.

Đôi khi bạn sẽ thấy đoạn mã lệnh cũ sau; nó tương đương với vararg2:

function vararg3(arg1,...)
  for i = 1,arg.n do
    print(arg[i])
  end
end

Tuy nhiên, arg được dùng theo cách này đã lỗi thời, và sẽ không áp dụng được với LuaJIT và Lua 5.2.

1.24 Làm thế nào để trả lại nhiều gía trị từ một hàm?

Một hàm Lua hoàn toàn có thể trả lại nhiều giá trị :

function sumdiff(x,y)
  return x+y, x-y
end

local a,b = sumdiff(20,10)

Lưu ý điểm độc lạ giữa Lua và Python ; Python trả lại nhiều giá trị bằng cách ghép chúng vào cùng một bộ ( tuple ), còn Lua thì trả lại nhiều giá trị theo đúng nghĩa của nó, và làm điều này rất hiệu suất cao. Phép gán sau tương tự với gán bội trong Lua

local a,b = 30,10

Nếu hàm của bạn thiết lập nên một bảng kiểu mảng thì nó có thể trả lại nội dung của mảng dưới dạng nhiều gía trị bằng unpack.

Một cách khác để hàm trả lại những giá trị là bằng cách đổi khác đối số bảng :

function table_mod(t)
  t.x = 2*t.x
  t.y = t.y - 1
end

t = {x=1,y=2}
table_mod(t)
assert(t.x==2 and t.y==1)

Ta hoàn toàn có thể làm điều này vì bảng luôn được truyền theo tham chiếu .

1.25  Bạn có thể truyền các biến được đặt tên cho một hàm không?

Các tham biến đặt tên thì rất thuận tiện so với hàm chứa nhiều tham biến, đặc biệt quan trọng là khi ta thường chỉ dùng một vài trong số đó. Không có cú pháp nào cho những tham biến đặt tên trong Lua, nhưng rất dễ tương hỗ chúng. Bạn truyền một bảng và khai thác đặc thù là không cần dùng thêm những cặp ngoặc nữa trong trường hợp hàm chỉ truyền một đối số dạng bảng :

function named(t)
  local name = t.name or 'anonymous'
  local os = t.os or ''
  local email = t.email or t.name..'@'..t.os
  ...
end

named {name = 'bill', os = 'windows'}

Lưu ý cách mà or hoạt động.

Phong cách truyền những tham biến đặt tên này có tương quan đến việc tạo mới một bảng mỗi lần gọi và nó không tương thích với những hàm sẽ được gọi rất nhiều lần .

1.26 Tại sao không có câu lệnh continue?

Đây là lời phàn nàn thường gặp. Những tác giả của Lua cảm thấy rằng continue chỉ là một trong những cơ chế mới để kiểm soát luồng thực thi chương trình (việc nó không thể hoạt động với quy tắc phạm vi trong repeat/until cũng là một yếu tố thứ hai.)

Trong Lua 5.2, có một câu lệnh nhảy goto để thuận tiện triển khai trách nhiệm .

1.27 Có cách xử lý biệt lệ không?

Các lập trình viên Lua thường thích trả lại lỗi từ các hàm. Quy tắc thông thường là nếu hàm trả lại nil hay false thì giá trị thứ hai được trả lại là lời thông báo lỗi.

function sqr(n)
  if type(n) ~= 'number' then
    return nil,'parameter must be a number'
  else
    return n*n
  end
end

Dĩ nhiên, ta biết rằng trong những ứng dụng lớn, tốt hơn thường là để lỗi ngay lập tức gián đoạn chương trình rồi bắt lỗi này bên ngoài. Điều đó giúp ta viết mã lệnh không quá nhồi nhét việc kiểm tra lỗi bằng điều kiện. Lua cho phép một hàm được gọi trong môi trường được bảo vệ qua lệnh pcall. Việc gói đoạn mã lệnh trong hàm khuyết danh sẽ cho ta hiệu quả tương đương như một cấu trúc try/catch:

local status,err = pcall(function()
  t.alpha = 2.0   -- sẽ phát ra lỗi nếu t là nil hay không phải một bảng 
end)
if not status then
  print(err)
end

Cách này có vẻ như hơi lủng củng, và tất cả chúng ta bị thôi thúc tìm một cách viết khác tinh xảo hơn. MetaLua cho ta một giải pháp đẹp bằng cách dùng những macro lúc biên dịch .
Một quyền lợi của dạng viết tường minh ở đây là ngân sách của việc xử lí lỗi đã hiện rõ ; xử lí lỗi đã tạo ra một closure ( đoạn lệnh đóng, xem mục 1.28.1 ) cho việc thực thi từng đoạn lệnh được bảo vệ .

Cái tương đương  với throw hay raise chỉ là hàm error.

Đối tượng bị ‘phát’ bởi error có thể là bất kì đối tượng Lua nào, nhưng nếu nó không phải chuỗi kí tự thì nó cần được chuyển về chuỗi. Ngoài ra, Lua 5.1 không áp dụng __tostring với những thông báo lỗi này khi thông báo một lỗi. Hãy xét:

MT = {__tostring = function(t) return 'me' end}
t = {1,2}
setmetatable(t,MT)
error(t)

Với Lua 5.1, lỗi được thông báo là “(error object is not a string)” (đối tượng lỗi không phải là chuỗi) và với Lua 5.2 đó là “me”, như dự liệu. Biểu hiện này không phải là vấn đề với các lỗi bị bắt một cách rõ ràng bằng pcall, vì bạn có thể trực tiếp xử lí đối tượng lỗi.

1.28 Không có lớp à? Vậy sao bạn lập trình được?

Đúng, không có câu lệnh class nào trong Lua, song lập trình hướng đối tượng (OOP) trong Lua thì không khó. Về một cách làm đơn giản cho thừa kế đơn, hãy xem SimpleLuaClasses; về tổng quát, hãy xem ObjectOrientedProgramming. Lời khuyên chung là, chọn lấy một bộ khung, đơn giản thôi, và thống nhất theo khung đó.

Sau đây là một mẫu từ nguồn tài liệu tìm hiểu thêm thứ nhất :

-- animal.lua

require 'class'

Animal = class(function(a,name)
   a.name = name
end)

function Animal:__tostring()
  return self.name..': '..self:speak()
end

Dog = class(Animal)

function Dog:speak()
  return 'bark'
end

Cat = class(Animal, function(c,name,breed)
         Animal.init(c,name)  -- must init base!
         c.breed = breed
      end)

function Cat:speak()
  return 'meow'
end

Lion = class(Cat)

function Lion:speak()
  return 'roar'
end

fido = Dog('Fido')
felix = Cat('Felix','Tabby')
leo = Lion('Leo','African')

D:\Downloads\func>lua -i animal.lua
> = fido,felix,leo
Fido: bark      Felix: meow     Leo: roar
> = leo:is_a(Animal)
true
> = leo:is_a(Dog)
false
> = leo:is_a(Cat)
true

Một số phương thức có tên đặc biệt, như __tostring (“metamethods”). Việc định nghĩa hàm này sẽ điều khiển cách biểu thị các đối tượng của ta dưới dạng chuỗi như thế nào.

Dạng hàm function TABLE:METHOD(args) là một cách viết giản tiện khác; nó hoàn toàn tương đương với function TABLE.METHOD(self,args). Nghĩa là, hai lời định nghĩa phương thức dưới đây tương đương nhau:

function Animal.feed(self,food)
...
end

function Animal:feed(food)
...
end

Vẻ đẹp của Lua nằm ở chỗ bạn có quyền tự do lựa chọn cách viết vừa lòng. Chẳng hạn, ta hoàn toàn có thể định nghĩa cách viết này :

class 'Lion': inherit 'Cat' {
   speak = function(self)
     return 'roar'
   end
}

Nhìn qua, có vẻ nó thật ảo, nhưng thực ra dòng đầu là class('Lion'):inherit('Cat'):({, bởi vậy câu lệnh đã khéo léo sử dụng đặc điểm của Lua chấp nhận các hàm có một đối số mà không cần bao trong ngoặc tròn nếu đối số đó thuộc kiểu chuỗi hay bảng. Một đối tượng ‘class’ dĩ nhiên không thể là một chuỗi, nên quy ước ở đây là các lớp được giữ trong bối cảnh module hiện thời (thường là bảng toàn cục). Khi đó, ta có cơ hội đặt tên các lớp; điều này hỗ trợ hữu ích cho việc gỡ lỗi – chẳng hạn, khi đó có thể dễ dàng đặt cho các lớp như vật một phương thức __tostring mặc định để in ra địa chỉ của chúng cùng với tên gọi.

Đa thừa kế hoàn toàn có thể được lập trình, nhưng sẽ tốn thêm thời hạn khi chạy so với những khung đơn thuần .
Mặt khác, đây là một tranh luận về việc nhất thiết phải có thừa kế không : “ Trong những ngôn ngữ trọn vẹn động, ở đó không có kiểm tra lỗi lúc biên dịch, thì không cần phải bảo vệ một cấu trúc chung định trước giữa những đối tượng người dùng tương đương. Một hàm đã cho hoàn toàn có thể nhận được bất kỳ kiểu đối tượng người tiêu dùng nào, miễn là nó thực thi được những phương pháp này nọ ” .

Vấn đề đi theo việc không có một khung OOP ‘thực thụ’ xuất hiện khi ta tích hợp mã lệnh Lua có dùng một khung khác không tương thích. Khi đó, tất cả những gì bạn phải giả định là một đối tượng có thể gọi được bằng cách viết a:f(). Việc dùng lại các lớp bằng thừa kế chỉ có thể khi bạn biết cấu trúc của lớp đó thế nào. Điều này có thể coi là một vấn đề làm cản trở sự chấp thuận dùng phong cách OOP cổ điển trong Lua.

Nếu thừa kế không thành yếu tố, thì cách viết sau đây sẽ nhanh gọn tạo nên những ‘ đối tượng người tiêu dùng béo ’ :

function MyObject(n)
    local name -- this is our field
    local obj = {}  -- this is our object

    function obj:setName(n)
        name = n
    end

    function obj:getName()
        return name
    end

    return obj
end
...
> o = MyObject 'fido'
> = o:getName()
fido
> o:setName 'bonzo'
> = o:getName()
bonzo

Cái bảng thực sự được trả lại chỉ chứa hai phương thức; trạng thái của đối tượng hoàn toàn được bao gói trong các closure. Biến phi-địa phương name (hay còn gọi là ‘upvalue’) biểu diễn cho trạng thái của đối tượng.

Lưu ý rằng nếu mã lệnh này được viết lại để cho ‘.’ được dùng thay cho ‘:’, thì bạn có thể dùng cách viết dấu chấm với những đối tượng này; nghĩa là o.getName(). Tuy nhiên đây không phải điều hay, vì những lập trình viên Lua đều dự liệu việc sử dụng ‘:’ và sau này bạn có thể phải thay đổi cách lập trình.

Nói chung, cách làm này cho ta năng lực điều động phương pháp nhanh nhất với giá phải trả là phát sinh những closure của phương pháp cùng với những bảng của chúng cho mỗi đối tượng người tiêu dùng. Nếu chỉ có ít đối tượng người tiêu dùng thôi thì cái giá này cũng đáng .

1.28.1 Closure là gì?

Trong Lua, sức mạnh của hàm nhiều phần là nhờ những closure. Nó giúp ta coi những hàm như những đối tượng người dùng được tạo theo cách động, như những bảng. Hãy xét hàm sau vốn trả lại một hàm khác :

function count()
   local i = 0
   return function()
      i = i + 1
      return i
   end
end

Mỗi lần được gọi, hàm này sẽ trả lại một closure mới .

> c1 = count()
> = c1()
1
> = c1()
2
> c2 = count()
> = c2()
1
> = c1()
3

Để điều này hoạt động được, biến i phải được xử lí một cách đặc biết. Nếu tất cả các hàm đều trả lại cùng một biết, thì chúng phải luôn trả lại giá trị được chia sẻ tiếp theo của i. Nhưng ở đây, mỗi hàm vẫn giữ một bản sao của i riêng cho mình – biến này được gọi là một giá trị đặc biệt (upvalue) của hàm. Như vậy một định nghĩa closure, đó là hàm kết hợp với upvalue bất kì. Điều này có ý nghĩa quan trojgn: các hàm (như những đối tượng) có thể bao gói trong nó những trạng thái. Đây là một lí do tại sao việc thiếu vắng khái niệm lớp nghiêm chỉnh lại không đáng ngại trong Lua.

Nó được cho phép lập trình theo phong thái hàm. Hãy xét việc tạo lập một hàm mới trong đó đối số thứ nhất được gắn vào một giá trị nào đó ( gọi là vận dụng hàm từng phần )

function bind1(val,f)
    return function(...)
        return f(val,...)
    end
end
...
> print1 = bind1('hello',print)
> print1(10,20)
hello   10  20

Một lần nữa, fval là những upvalue; mỗi lần gọi mới bind1 đều tạo ra một closure mới với tham chiếu riêng của nó đến fval.

1.29 Có cách nội suy chuỗi, chẳng hạn như “$VAR is expanded” không?

Không trực tiếp được hỗ trợ, nhưng bạn vẫn dễ dàng làm được bằng string.gsub.

res = ("$VAR is expanded"):gsub('%$(%w+)',SUBST)

$ rất ‘kì diệu’, bởi vậy ta cần cho nó thoát nhóm chuỗi, và dạng mẫu %w+ cho ‘chữ cái hoặc chữ số’ được giữ lại truyền cho SUBST như ‘VAR’. (Một dạng mẫu chuẩn hơn cho tên biến trong Lua là ‘[_%a][_%w]*’).

Ở đây SUBST có thể là một chuỗi, một hàm hay một bảng chứa các cặp khóa-trị. Bởi vậy việc dùng os.getenv sẽ cho phép ta mở rộng các biến môi trường và dùng thứ kiểu như {VAR = 'dolly'} sẽ thay thế kí hiệu này với các gía trị tương ứng trong bảng.

Cosmo đã đóng gói và lan rộng ra dạng mẫu này một cách bảo đảm an toàn với những mẫu con :

  require "cosmo"
  template = [==[
  

$list_name

    $do_items[[
  • $item
  • ]]
]==] print(cosmo.fill(template, { list_name = "My List", do_items = function() for i=1,5 do cosmo.yield { item = i } end end } )) ==>

My List

  • 1
  • 2
  • 3
  • 4
  • 5

Cách định dạng chuỗi kiểu Python hoàn toàn có thể triển khai được trong Lua :

print( "%5.2f" % math.pi )

print( "%-10.10s %04d" % { "test", 123 } )

Cách lập trình rất ngắn và hay :

getmetatable("").__mod = function(a, b)
        if not b then
                return a
        elseif type(b) == "table" then
                return string.format(a, unpack(b))
        else
                return string.format(a, b)
        end
end

Các chuỗi Lua đều chia sẻ một bảng meta, bởi vậy mã lệnh này đè lên toán tử % (chia dư) trong mọi chuỗi.

Với giải pháp này cũng như những cái khác, xem trang Nội suy chuỗi trên Wiki .
LuaShell là một ứng dụng trình diễn rất hay trong đó nội suy chuỗi và tự động hóa tạo hàm hoàn toàn có thể cho bạn một ngôn ngữ lập trình dòng lệnh tốt hơn :

require 'luashell'
luashell.setfenv()

-- echo là một hàm lập sẵn
-- lưu ý rằng môi trường và các biến địa phương đều truy cập được qua $
echo 'local variable foo is $foo'
echo 'PATH is $PATH'

cd '$HOME'    -- cd cũng là hàm lập sẵn

-- hàm ls được tạo ra một cách động khi được gọi 
-- hàm ls sẽ rẽ nhánh và thực hiện /bin/ls
ls ()
ls '-la --sort=size'

1.30 Các lời gọi đuôi được tối ưu thì dùng vào việc gì?

Đôi khi một yếu tố hoàn toàn có thể được diễn đạt tự nhiên theo hướng đệ quy, nhưng có vẻ như đệ quy quá tốn kém vì thường toàn bộ những khung ngăn xếp đều phải được lưu trong bộ nhớ .
Hàm sau đây định nghĩa một vòng lặp vô hạn trong Lua :

function F() print 'yes'; return F() end

Hãy xét cách làm này cho một Máy Trạng thái Hữu hạn ( Finite State Machine, FSM )

-- Định nghĩa trạng thái A
function A()
  dosomething"in state A"
  if somecondition() then return B() end
  if done() then return end
  return A()
end

-- Định nghĩa trạng thái B
function B()
  dosomething"in state B"
  if othercondition() then return A() end
  return B()
end

-- Chạy máy FSM, bắt đầu từ trạng thái A
A()

Máy này luôn ở một trạng thái A hay B, và có thể chạy vô hạn vì các lời gọi đuôi được tối ưu hóa về thứ tương tự như một lệnh goto.

1.31 Về việc đóng gói thành ứng dụng độc lập?

srlua triển khai việc làm cơ bản ; bạn hoàn toàn có thể gắn một đoạn mã lệnh Lua vào với Lua interpreter, và đưa ra một file thực thi độc lập .
Tuy nhiên, phần nhiều những file lệnh quy mô hẳn hoi đều có mã lệnh nhờ vào ; L-Bia nhằm mục đích phân phối một giải pháp tổng lực hơn .

Một giải pháp tốt là Squish vốn là một công cụ để nhồi nhiều file mã lệnh Lua cùng những module của chúng vào một file Lua duy nhất, để từ đó xử lý bằng srlua.

1.32 Làm thế nào để tôi nạp và chạy mã lệnh Lua (vốn nhiều khả nghi)?

Nhiệm vụ là phải thực thi được mã lệnh có nguồn gốc đáng ngờ .

-- tạo lập một môi trường
local env = {}

-- chạy mã lệnh trong môi trường này 
local function run(code)
  local fun, message = loadstring(code)
  if not fun then return nil, message end
  setfenv(fun, env)
  return pcall(fun)
end

Đầu tiên, mã lệnh được biên dịch thành một hàm, sau đó ta đặt môi trường cho hàm này, rồi cuối cùng hàm đó được gọi bằng pcall, nhờ đó ta có thể bắt được lỗi. Dĩ nhiên, bạn có thể dùng loadfile ở đây.

Lưu ý rằng nếu bạn muốn nạp mã lệnh rồi thực hiện nó lặp đi lặp lại thì bạn nên  you should store the compiled function and pcall it whenever needed.

Mã lệnh có thể tới từ bất kì đâu. Đây là vấn đề ‘sand-box’ kinh điển (xem Sandboxing.) Mấu chốt của việc làm cho mã lệnh đó phải càng an toàn càng tốt là hạn chế mối trường của hàm. Nếu không có lời gọi setfenv, hàm đó sẽ chạy trong môi trường toàn cục và dĩ nhiên có thể gọi được bất kì hàm Lua nào, do đó tiềm năng gây nguy hại. Như vậy cách tiếp cận sand-boxing bao gồm việc bắt đầu với một môi trường trống không và chỉ bổ sung những hàm nào bạn muốn người khác dùng đến, ngoại trừ những hàm ‘không an toàn’.

Với việc bao gồm những module của riêng bạn vào trong môi trường sand-box,  cần lưu ý rằng module('mymod',package.seeall) rõ ràng là một rủi ro an toàn. Lời gọi này hoạt động bằng cách tạo nên một môi trường mới trong bảng mymod, và sau đó đặt __index_G – sao cho bất kì ai cũng có thể truy cập các hàm toàn cục qua module của bạn, đơn giản chỉ bằng cách mymod._G ! Do vậy, hãy hạn chế sử dụng package.seeall.

Với Lua 5.2, một hàm tương tự sẽ là :

function run (code)
   local fun, message = load(code,"tmp","t",env)
   if not fun then return nil, message end
   return pcall(fun)
end

1.33 Về việc nhúng Lua vào trong một ứng dụng?

Một trong những hiệu quả đáng kể nhất của Lua là trong việc trưng ra những chương trình con bên trong trải qua trình thông dịch hiệu suất cao của Lua, giusp cho người dùng và người tăng trưởng hoàn toàn có thể lan rộng ra và tùy biến tính năng của một trình ứng dụng. Lua cũng là một ứng viên lí tưởng cho những mã lệnh phân theo module và chạy trên nhiều nên tảng khác nhau bên trong trình ứng dụng, hay giữa những phần của ứng dụng đó. Những chương trình ứng dụng có dùng đến Lua theo cách này gồm có Adobe Lightroom, World-of-Warcraft, VLC, Lighttpd, và còn nữa .
Việc nhúng Lua chỉ làm tăng thêm 150 – 200K vào dự án Bất Động Sản của bạn, tùy theo những thư viện được chọn là gì. Lua được phong cách thiết kế là ngôn ngữ lan rộng ra và rất dễ bảo vệ rằng những đoạn lệnh do người dùng bổ trợ đều hoạt động giải trí trong một môi trường tự nhiên ‘ bảo đảm an toàn ’ ( xem Sandboxing. ) Thậm chí bạn không phải nhúng mặt tiền của trình biên dịch Lua, và chỉ dùng phần cốt lõi với những đoạn mã lệnh đã biên dịch sẵn. Điều này sẽ giúp giảm nhu yếu bộ nhớ xuống chỉ còn khoảng chừng 40K .
Những nguyên do khiến Lua thành một ngôn ngữ lan rộng ra lý tưởng đều gắn với những lời phàn nàn thường thấy từ những người viết mã lệnh rằng Lua ‘ không đi kèm theo những bộ công cụ thêm ( battery ) ’. Phần lõi của Lua phụ thuộc vào chặt vào những thứ cung ứng bởi ANSI C, do vậy không có thứ gì nhờ vào vào nền tảng ( mạng lưới hệ thống ). Thế nên, việc tích hợp nó vào trong dự án Bất Động Sản của bạn thường rất thuận tiện .
Việc tích hợp với C / C + + đặc biệt quan trọng rất dễ ( xem mục 4.4 hay Ví dụ API đơn thuần ), còn những cách buộc qua lại với những ngôn ngữ hay nền tảng thông dụng đều hoàn toàn có thể được ( xem cách buộc mã lệnh vào với Lua ) .

1.34 Về việc viết tài liệu cho mã lệnh Lua?

Mã lệnh so với máy tính là một thứ để tiếp xúc với con người : như Donald Knuth đã nói “ Thay vì tưởng tượng ra trách nhiệm chính của người là hướng dẫn cho máy biết phải làm gì, thì ta hãy tập trung chuyên sâu vào việc lý giải cho người biết những điều mà ta muốn máy làm ”. ( Sau một vài tháng, bạn chính là người cần lời lý giải đó. )
LuaDoc thường hay được dùng nhất. Phong cách của nó tựa như với những công cụ phát sinh tài liệu khác như JavaDoc :

--- Bắt đầu bằng ba dấu gạch ngang; đây là câu tóm lược.
-- Bạn có thể viết thêm các dòng ở đây;
-- Chúng có thể chứa HTML, và thực ra bạn cần viết rõ 
-- để xuống dòng. -- @param first tên riêng của người (string) -- @param second tên họ của người (string) -- @param age số tuổi; (optional number) -- @return một đối tượng (Person) -- @usage person = register_person('john','doe') -- @see Person function register_person(first,second,age) ... end

Chú thích đoạn mã của bạn theo cách có cấu trúc như trên dù sao cũng rất có ích, bởi Lua không ghi rõ kiểu của những đối số trong hàm. Nếu người dùng phải đoán xem cần truyền vào hàm dữ liệu nào qua việc đọc mã lệnh bên trong thì chứng tỏ mã lệnh đó chưa được viết tài liệu kĩ lưỡng. Hoặc việc đặt quy ước cho kiểu đối số cũng có ích .
Tuy nhiên, những tài liệu tự động hóa phát sinh thì hiếm khi phân phối đủ. Một bộ mã lệnh thử nghiệm được viết tài liệu kĩ mới có ích gấp đôi trong trường hợp này .

1.35 Làm cách nào để kiểm tra kiểu của các đối số thuộc hàm?

Việc lao lý kiểu tài liệu động đưa tới mã lệnh đơn thuần và tổng quát. Hàm

function sqr(x) return x*x end

sẽ hoạt động với mọi gía trị có thể nhận được, nghĩa là một con số hoặc một đối tượng đã định nghĩa phương thức meta tên __mul. Đồng thời, do type() trả lại kiểu Lua thực sự, ta có thể làm nhiều việc tùy theo những gì ta đã truyền. Khỏi cần nói, sự linh hoạt này có cái gía của nó, đặc biệt là trong những chương trình lớn. Việc lập tài liệu kĩ lưỡng cho hàm trở nên thiết yếu (xem mục 1.34).

Dĩ nhiên, trình biên dịch không đọc phần tài liệu này. Có những cách kiểm tra kiểu trong đó ta nhúng thông tin về kiểu vào trong mã lệnh Lua. Một phong cách lập trình là dùng assert đối với mọi đối số hàm:

function sqr(x)
    assert(type(x) == "number", "sqr expects a number")
    return x*x
end

Bây giờ ta thu được một lời thông báo ý nghĩa rõ ràng, với gía phải trả là một đoạn lệnh viết thêm và có thể nhiều thời gian chạy chương trình. Trong Lua, assert() không phải là một macro như ở C, bởi vậy không thể đơn giản mà ngắt nó đi được. Số lượng mã lệnh phải gõ thêm vào có thể được giảm bớt bằng cách lập các hàm phụ trợ thích hợp, nhưng vẫn còn rất nhiều phép kiểm tra. Một vấn đề khác là các kiểu dữ liệu đối số hàm được gói trọn trong phần lập trình của hàm đó, và vì vậy không thể truy cập được mà không thực hiện qua phân tích mã lệnh một cách khéo léo (dù rằng cách làm này không vững chắc).

Một cách làm đầy triển vọng là dùng những decorator :

sqr = typecheck("number","->","number") ..
function (x)
    return x*x
end

Bây giờ dấu ấn (signature) của sqr đã rõ ràng và nổi lên trên, dễ dàng tách được khỏi mã nguồn, và việc kiểm tra kiểu dữ liệu khi chạy thực sự có thể tắt/bật được.

Sự ràng buộc về kiểu như ‘number’ ở đây là quá cụ thể, nhưng ta có thể định nghĩa và sử dụng các hàm true/false như is_number().

Bạn rất nên xem xét sử dụng Metalua, vốn là một bộ biên dịch macro cho Lua mưu trí và lan rộng ra được ( theo nghĩa vừa đủ của Lisp về những ‘ macro sạch ’ ( hygenic macro ) ). Cú pháp này trở nên hợp lệ :

-{ extension "types" }

function sum (x :: list(number)) :: number
    local acc :: number = 0
    for i=1, #x do acc=acc+x[i] end
    return acc
end

1.36 Làm thế nào để kết thúc thực thi một cách êm thấm?

Hàm error sẽ phát ra một lỗi với lời thông báo cho trước, và nếu nó không được xử lý thì pcall sẽ tạo ra một stack trace. Điều này hữu ích khi phát triển chương trình, song không đẹp mắt những người sử dụng. Thường ta lập một hàm quit kiểu như:

function quit(msg, was_error)
    io.stderr.write(msg,'\n')
    os.exit(was_error and 1 or 0)
end

os.exit kết thúc chương trình ngay, không cần phải kích hoạt bộ thu gom rác bộ nhớ. (Trong Lua 5.2 có một tham số thứ hai, tùy chọn, cho os.exit để đóng trạng thái Lua và bắt buộc việc thu rác.)

The behaviour of error can be modified by setting debug.traceback=nil so that it doesn’t produce a stack trace. (This is a nice example of Monkey patching; modifying Lua’s operation by changing a library function dynamically.)

Cách làm vá víu này cũng ảnh hưởng hàm assert. assert nhận hai đối số, một điều khiện phải-đúng và một thoogn báo lỗi. Nó không phải là một câu lệnh và trả về đối số thứ nhất. Vì các hàm Lua thường trả về một gía trị nil và chuỗi báo lỗi khi thất bại trong qúa trình chạy, song cách viết sau lại hoạt động được vì đối số thứ nhât truyền cho assert sẽ là nil, và đối số thứ hai sẽ là chuỗi báo lỗi.

f = assert(io.open(file))

Thường những người dùng một module chỉ muốn xem stack trace những gì lên quan đến mã lệnh của bản thân họ, chứ không quan tâm những chi tiết bên trong của lỗi (bất kì ai đã dùng một thư viện Java với biểu hiện hỏng thì đều biết việc này.) Cái stack trace tạo bởi error có thể điều khiển được thông qua một tham biển ‘level’ tùy chọn để chỉ định chỗ nào trong stack gọi (call stack) phải chịu trách nhiệm với lỗi được phát sinh. Một giải pháp tổng quát hơn được Lua Carp thực hiện, vốn được phỏng theo module carp của Perl.

1.37 module() làm việc như thế nào?

module được dùng để tạo ra một module mà sau đó có thể được require. Dù không bắt buộc, song nó đơn giản hóa việc tạo lập các module chuẩn của Lua.

-- testm.lua (đâu đó trong đường dẫn Lua)
local print = print
module ("testm")

function export1(s)
    print(s)
end

function export2(s)
    export1(s)
end

Sau lệnh require ("testm") bạn có thể gọi các hàm đã xuất khẩu theo dạng testm.export2("text") v.v. Bởi vậy điều đầu tiên module thực hiện là tạo lập một bảng chứa các hàm mới của bạn; nó cũng đảm bảo rằng require sẽ trả lại bảng này (thay vì chỉ trả lại true).

Thứ hai là, nó đặt môi trường hàm của đoạn mã lệnh thành cái bảng này. Điều này khiến cho nó hoạt động như một không gian tên (‘namespace’) ở các ngôn ngữ khác; bên trong module testm, bất kì hàm nào cũng có thể trực tiếp thấy các hàm khác mà không phải đề cập đến testm. Điều này nghĩa rằng bất kì hàm nào đều mặc định là ẩn, bởi vậy ta đã phải tạo một bí danh địa phương đến print.

Người ta hoàn toàn có thể thấy điều này thật hạn chế, vì thế module thường được khai báo như :

module ("testm",package.seeall)

Điều này làm thay đổi bảng module sao cho mọi trường hợp tra cứu không thỏa đều được thực hiện trong phạm vi toàn bộ, bởi vậy dòng khai báo cục bộ của print trở nên thừa. (Điều này được làm bằng cách cho bảng đó một bảng meta với __index chỉ đến bảng toàn cục _G); lưu ý rằng việc truy cập các biến toàn cục thế này sẽ chậm hơn so với khai báo rõ là biến cục bộ.

Có thể viết  module (...) ở đầu module. Khi được nạp, module này sẽ được gọi với tên của module được xác định thông qua require. Điều đó nghĩa là bản thân module không chứa tên của nó, và có thể được di chuyển tùy ý.

Ý nghĩa thiết yếu của module được chứa trong đoạn mã sau:

function mod(name)
   module ('test',package.seeall)
   function export()
    print 'export!'
   end
end

mod('test')

test.export()

Như vậy, lời gọi module biến môi trường của hàm mod thành test, sao cho bất kì hàm nào được định nghĩa bên trong đều thực ra là ở trong bảng test.

1.37.1 Lời chỉ trích về module()

Lời chỉ trích thứ nhất là là package.seeall bộc lội bảng toàn cục trong những trường hợp bạn thự sự muốn hạn chế truy cập đến tính năng. Nó dẫn đến việc ‘bao gói rò rỉ’ khiến cho bất kì người dùng module này dễ dàng truy cập các hàm hay các bảng toàn cục thông qua bản thân module đó, ví dụ testm.io.

Chỉ trích thứ hai là một module có dùng module để tạo nên các bảng toàn cục và xuất khẩu các đối tượng phụ thuộc. module "hello.world" tạo ra một bảng hello (nếu chưa có sẵn) và world là một bảng bên trong nó. Nếu hello.world yêu cầu fred thì fred sẽ tự động trở thành có sẵn với tất cả những ai dùng hello.world, vốn có thể lệ thuộc vào chi tiết mã lệnh đã lập trình và họ sẽ nhầm lẫn nếu mã lệnh thay đổi.

Xem thông tin ở đây .

Lưu ý rằng module() đã lỗi thời từ Lua 5.2; các nhà phát triển nên dùng một phong cách đơn giản, không màu mè để viết nên module.

1.37.2 Mọi thứ sau module()?

Phong cách viết module này vẫn còn quy đổi tốt giữa Lua 5.1 và Lua 5.2, ở đó mọi hàm xuất khẩu đều sẽ được đặt vào một bảng module :

-- mod.lua
local M = {}

function M.answer()
  return 42
end

function M.show()
   print(M.answer())
end

return M

Lưu ý rằng không có biến toàn cục nào được tạo ra, và trong thực tiễn không có tên module nào được chỉ định. Bởi vậy bạn cần gán module cho một biến trước khi sử dụng :

local mod = require 'mod'

Nhược điểm của phong cách ‘không màu mè’ này là ở chỗ mỗi tham chiếu hàm module phải được đề cập rõ module chủ (tức là phải qualify); đây có thể là vấn đề khi ta chuyển đổi mã lệnh bằng module(). Phương án sau khai báo các hàm ngay từ đầu:

local answer, show

function answer()  return 42 end

function show() print(answer()) end

return {answer=answer,show=show}

Cách này có ưu điểm rằng : ( a ) hàm xuất khẩu được đề cập rõ ở cuối và ( b ) mọi truy vấn đến hàm đều có tính cục bộ và do vậy sẽ nhanh hơn. ( Những module chứa rất nhiều hàm sẽ đạt đến số lượng giới hạn biến cục bộ vào tầm 240, tuy nhiên hoàn toàn có thể đây là trường hợp quá hiếm. )

1.38 Các bảng yếu là gì?

Để hiểu được bảng yếu, bạn cần hiểu một yếu tố chung trong tịch thu rác bộ nhớ. Bộ tịch thu rác phải bảo thủ ở mức cao nhất hoàn toàn có thể, và không suy đoán bất kỳ điều gì về tài liệu ; nó không phải nhà tuyên đoán. Ngay khi những đối tượng người tiêu dùng được giữ trong một bảng, chúng được coi là đã tham chiếu đến và sẽ không bị tịch thu miễn là bảng đó được tham chiếu .

Các đối tượng được tham chiếu bởi một bảng yếu sẽ được thu hồi nếu không có tham chiếu sẽ bị thu hồi nếu không có tham chiếu đến chúng. Nếu một metatable của bảng có trường tên là __mode thì nó sẽ trở thành bảng yếu; __mode có thể là một trong hai ‘k’ (khóa yếu) hay ‘v’ (giá trị yếu), hoặc cả hai. Việc đặt một giá trị vào trong bảng với các giá trị yếu sẽ đồng nghĩa với việc bảo bộ thu hồi rằng đây không phải một tham chiếu quan trọng và có thể được thu hồi một cách an toàn.

Lưu ý rằng chuỗi không được coi là một giá trị ( như số, khác với một đối tượng người dùng như bảng ) về phương diện này. Điều đó bảo vệ cho việc chuỗi kí tự không bị tịch thu khỏi bảng với khóa yếu .

1.39 Khác biệt giữa những cách viết chuỗi kí tự?

Không có độc lạ gì giữa chuỗi trong nháy đơn hay nháy kép, và bạn hoàn toàn có thể dùng một trong hai cách, mặc dầu thống nhất sử dụng sẽ tốt hơn. Chẳng hạn, người ta dùng nháy đơn cho những chuỗi bên trong chương trình và nháy kép cho những chuỗi được cho phép người dùng / mạng lưới hệ thống xem .

Những chuỗi này có thể chứa các kí tự thoát kiểu C (như \n) và lưu ý rằng 12 chính là hằng số 12 trong hệ thập phân ! Các dấu [[..]] bao quanh chuỗi dài thì không đọc những kí tự thoát này. Theo quy ước, dấu kết thúc dòng thứ nhất sẽ không được tính đến, như:

s = [[
Một chuỗi với chỉ một dấu kết thúc dòng.
]]

Cái giá phải trả cho sự đơn giản này là các biểu thức như T[A[i]] sẽ không được hiểu đúng, bởi vậy Lua cho phép [=[...]=] trong đó số lượng các dấu = phải như nhau. (Đây cũng là chế độ dùng cho những lời chú thích kéo dài, với dấu -- đứng trước.) Lưu ý rằng với Lua 5.0 cặp [[..]] vẫn được cho phép, song đặc điểm này đã lỗi thời.

Do phải giải quyết và xử lý dấu kết thúc dòng nên những chuỗi dài như thế này không thích hợp cho việc nhúng tài liệu nhị phân trực tiếp vào chương trình .

1.40 Các vấn đề tương thích giữa Windows và Unix?

Ở đây, ‘ Unix ’ là để nói chung cho hệ quản lý tựa-POSIX như Linux, Mac OS X, Solaris, v.v.

package.config là một chuỗi trong đó ‘kí tự’ đầu tiên là dấu phân cách thư mục, bởi vậy package.config:sub(1,1) là dấu gạch chéo xuôi hay gạch chéo ngược. Một quy tắc chung là nên dùng mã này khi lập các dường dẫn.

Một khác biệt lớn giữa các bản dựng cho Windows và Unix là package.path mặc định được đặt ở vị trí của file chạy trong Windows, còn ở  Unix thì đặt tại /usr/local/share/lua/5.1. Bởi vậy sẽ dễ hơn nếu ta cài đặt Lua cho người dùng trong Windows, nhưng Lua tôn trọng các biến môi trường LUA_PATHLUA_CPATH.

Lua phụ thuộc trực tiếp hơn vào các thư viện chạy (runtime) C của hệ thống, hơn đa số ngôn ngữ văn lệnh khác, nên bạn phải để ý những khác biệt về hệ thống. Hãy dùng kí hiệu “rb” với io.open nếu bạn cần tương thích với khâu nhập/xuất số liệu nhị phân Windows. Hãy cẩn thận với os.tmpname vì nó không trả lại một đường dẫn đầy đủ với Windows (chèn giá trị của biến môi trường TMP lên đầu tiếp theo là dấu gạch chéo ngược.) os.clock được thực hiện rất khác trên Windows.

Tương tự, os.time có thể sẽ làm đổ vỡ Lua nếu truyền các kí hiệu định dạng không tương thích. (Điều này không còn là vấn đề trong Lua 5.2, bởi phiên bản này trước hết sẽ thực hiện khâu kiểm tra.)

Với hệ GUI Windows, os.execute có thể sẽ gây khó chịu, và io.popen đơn giản sẽ không hoạt động – song có những thư viện mở rộng cho việc đa nền sẽ giúp ích trong trường hợp này.

( Xem tiếp Phần 2 )

Đánh giá:

Share this:

Thích bài này:

Thích

Đang tải …