Lua:只有少數(shù)程序員知道的最簡單、功能齊全的語言
創(chuàng)建一種簡單易學的解釋型編程語言并非難事。我們只需根據(jù)簡單的語言規(guī)范,使用任何喜歡的編程語言編寫一個解析器和語句運行程序。為了提高性能,我們可以使用基于字節(jié)碼的執(zhí)行系統(tǒng),而不是像 Bash 解釋器那樣直接執(zhí)行解析后的源代碼。任何人都可以創(chuàng)造出簡單易學的語言,但每一種易學的語言都不會成為有用的通用語言。例如,創(chuàng)建一種只支持基本算術運算的簡單腳本語言,并不會成為一種人人都能用來編程的有用語言。
大多數(shù)開發(fā)人員認為 Python、Ruby 和 JavaScript 是易于學習、簡單且有用的語言。這些簡單的語言提供了開發(fā)人員友好、高效、簡單的語法,激勵每個程序員在基于社區(qū)的庫的幫助下使用它們來構建任何軟件項目。毫無疑問,Python 提供了比 JavaScript 語言更簡單的語法——對于大多數(shù)程序員來說,編寫 Python 代碼就像編寫偽代碼一樣。Python 是有史以來最簡單(但有用)的語言嗎?
Lua 是一種動態(tài)類型、輕量級、可嵌入、功能齊全的通用語言,比 Python 更容易學習。大多數(shù)游戲開發(fā)人員都知道 Lua 語言,因為它廣泛用作基于 C/C++ 的游戲引擎中的嵌入式腳本語言。然而,大多數(shù)非游戲開發(fā)者并不了解 Lua——他們仍然認為 Python 是世界上最簡單的語言。在這個故事中,我們將探索 Lua 提供的簡單性。
Lua,一種只需幾分鐘即可掌握的語言,而不是幾年
編程語言的學習曲線各不相同。有些語言關鍵字少、語法簡單、獨特的核心概念少,因此學習起來比較快。與此同時,如果某些語言引入的獨特概念超出了傳統(tǒng)的理論編程概念,那么對于新手來說就會變得更加復雜。
Lua 是一種簡單的語言,您可以使用所擁有的計算機科學知識來掌握它 - 您不需要學習任何超出核心編程基礎知識的獨特語言即可成為 Lua 專家。
Lua 只有 22 個關鍵字、8 種數(shù)據(jù)類型和一種可以用來構建任何復雜結構的數(shù)據(jù)結構。如果你知道如何編寫理論上的偽代碼,你就可以用 Lua 編寫計算機程序——編寫 Lua 代碼就像在紙上寫偽代碼一樣:
function fact(n)
if n == 1 then
return 1
end
return n * fact(n - 1)
end
print("fact(3) = " .. fact(3)) -- fact(3) = 6
print("fact(5) = " .. fact(5)) -- fact(5) = 120
看看上面的遞歸階乘數(shù)生成程序的簡單性。它沒有使源代碼變得復雜的花哨語法——語言語法對于大多數(shù)開發(fā)人員來說是不言自明的。Lua 定義了一個帶有 end 關鍵字的控制塊,類似于經(jīng)典的偽代碼。它使用 .. 進行串聯(lián),并使用 -- 作為單行注釋的前綴。
您甚至可以編寫一行 if 塊,如下所示:
function fact(n)
if n == 1 then return 1 end
return n * fact(n - 1)
end
Lua 提供了比 Python 更簡單的數(shù)值 for 循環(huán)語法:
for i = 1, 10 do
print(i)
end
在幾乎所有情況下,Lua 都致力于通過保持整體語言的簡單性來最佳地重用現(xiàn)有語法,而不引入新語法??纯瓷厦娴臄?shù)值 for 循環(huán)是如何使用賦值運算符的。有些語言看起來很簡單,但它們有許多隱藏的概念和語法,因此開發(fā)人員可能需要花費數(shù)年時間來掌握它們,即使他們可以在幾分鐘內開始使用這些語言。
一切都只有一種數(shù)據(jù)結構
現(xiàn)代編程語言通常提供多種預開發(fā)的數(shù)據(jù)結構,如數(shù)組、列表、映射、隊列、向量、集合等,但我們在大多數(shù)程序中只使用少數(shù)幾種數(shù)據(jù)結構。當一種特定的編程語言增加了新的數(shù)據(jù)結構時,它可能會通過影響語言的最小設計來為每種結構引入一種新的語法,例如,Python 有三種初始化三種數(shù)據(jù)結構的語法:
type([1, 2]) #
type((2, 5)) #
type({"a": 10, "b": 20}) #
Lua 僅使用稱為表結構的關聯(lián)數(shù)組結構來處理所有事情。它允許用戶僅使用一種基于大括號的語法來創(chuàng)建數(shù)組、映射和任何其他內容:
local array = {1, 4, 10, 12}
array[1] = 10
print(array[1]) -- 10 (array index starts from 1 in Lua)
local map = {width = 200, height = 100}
map["width"] = 250
map.width = 350
print(map.width) -- 350
print(type(array), type(map)) -- table table
上面的代碼片段創(chuàng)建了帶有表的 array 數(shù)組。當我們不使用關聯(lián)鍵值對時,表實例可以作為具有數(shù)字索引的傳統(tǒng)數(shù)組進行訪問。Lua 允許您使用帶有類似于 C 結構體初始化的賦值運算符的表來創(chuàng)建映射。當您使用表結構創(chuàng)建地圖時,您可以使用類似 JavaScript 的屬性訪問語法,如上面的 map 變量所示。
上面的代碼片段使用 local 關鍵字使這些變量成為本地變量,因為 Lua 是一種詞法范圍的簡單語言。Lua 擁有有史以來最簡單的語法來獲取數(shù)組的長度:
print(#{1, 2, 5, 1}) -- 4
local arr = {1, 2}
print(#arr) -- 2
為現(xiàn)代開發(fā)人員提供友好、高效的環(huán)境
每種極簡語言都可能無法為現(xiàn)代開發(fā)人員提供友好、高效的語法和功能。例如,C 語言無疑是一種只有 32 個關鍵字的極簡語言,但它并沒有為現(xiàn)代開發(fā)人員提供友好、高效的環(huán)境,因為它沒有映射結構、動態(tài)列表、高效的字符串處理方法、自動內存管理(垃圾回收)、基于 OOP 的功能以及注重生產(chǎn)力的速記功能。
Lua 是一種極簡語言,但它經(jīng)過精心設計,以極簡的方式滿足開發(fā)人員的每一個需求。Lua 允許您使用類似 Python 的現(xiàn)代方法迭代數(shù)組和映射:
local vowels = {"a", "e", "i", "o", "u"}
for i, v in ipairs(vowels) do
print(i, v)
end
print("----")
local scores = {john = 120, david = 80, ann = 120, julia = 52}
for k, v in pairs(scores) do
print(k, v)
end
上面的Lua代碼片段通過使用 ipairs() 和 pairs() 全局可迭代函數(shù)打印 vowels 和 scores 結構體的內容,如下所示以下預覽:
圖片
Lua 支持多重賦值和多個函數(shù)返回值,作為一種對開發(fā)人員友好的現(xiàn)代語言:
local a, b = 10, 20
print(a, b) -- 10 20
function getsize()
return 20, 30
end
local w, h = getsize()
print(w, h) -- 20 30
Lua 是一種多范式語言,因此它提供了函數(shù)式和面向對象風格的功能。例如,它允許您創(chuàng)建 lambda 函數(shù),如下所示:
function exec(func)
print("Running lambda...")
func()
end
exec(function() print("Lua") end) -- Lua
exec(function() end) -- (empty function)
Lua 不像 C# 或 Java 等大多數(shù)以行業(yè)為中心的編程語言那樣提供那么多內置的 OOP 功能,但它提供了類似 Go 的最小類創(chuàng)建,而沒有內置的繼承功能:
Rect = {}
function Rect:new(width, height)
self.width = width
self.height = height
return setmetatable({}, {__index = self})
end
function Rect:area()
return self.width * self.height
end
local rect = Rect:new(100, 50)
print(rect:area()) -- 5000
local square = Rect:new(50, 50)
print(square:area()) -- 2500
在這里,我們使用 Rect 表結構創(chuàng)建了一個類,并通過 setmetatable() 內置函數(shù)和 __index 創(chuàng)建元表,將類屬性和方法附加到表中元方法。您還可以通過使用元表構建原型系統(tǒng)來實現(xiàn)繼承。從官方文檔中了解有關元表的更多信息。
最小但功能強大的標準庫
Lua 有一個最小但功能強大的預導入標準庫,提供數(shù)學函數(shù)、文件處理、操作系統(tǒng)函數(shù)、非搶占式多線程、調試、字符串操作、表操作以及與動態(tài)鏈接庫通信的功能。Lua的標準庫也是多范式的,這意味著,你可以通過傳遞標識符來調用標準庫函數(shù),也可以將它們作為綁定對象的方法來調用。
例如,請參閱以下示例 Lua 代碼片段如何調用字符串函數(shù)/方法:
local msg = "Lua"
print(string.lower(msg)) -- lua (Using the functional style)
print(msg:lower()) -- lua (Using the OOP style)
print(string.reverse(msg)) -- auL
print(string.sub(msg, 1, 2)) -- Lu
在標準庫中 OOP 風格的支持下,您可以高效地鏈接字符串方法,如下所示:
local msg = "Hello Lua"
print(msg:sub(7):lower():reverse()) -- aul
Lua 沒有實現(xiàn) Regex,因為它會影響 Lua 嵌入程序的大小和 Lua 參考實現(xiàn)的復雜性,因此它提供了一個輕量級的類似 Regex 的但最小模式匹配的實現(xiàn),如以下示例所示:
local productcode = "BL-202 AL-233"
for prefix, num in string.gmatch(productcode, "([A-Z]+)-(%d+)") do
print(prefix, num) -- BL 202 .. AL 233
end
典型的 Regex 實現(xiàn)需要編寫 4000 多行代碼,但 Lua 用不到 500 行代碼實現(xiàn)了自己的類似 Regex 的輕量級模式匹配解決方案:
圖片
Lua 提供了一種讀取標準輸入流的簡單方法,因此構建 REPL 程序非常高效,如以下示例所示:
local lastname = ""
while 1 do
io.write("Enter your name: ")
input = io.read()
if input == ":exit" then
print("Goodbye " .. lastname)
break
end
print(string.format("Hello %s, Welcome", input))
lastname = input
end
圖片
Lua 中的文件操作確實也非常高效。看下面讀取并打印 Lua 源文件內容的示例:
local file = io.open("main.lua", "rb")
print(file:read("*all"))
Lua還通過 os 模塊導出操作系統(tǒng)級操作,并提供一種通過 package 模塊調用動態(tài)鏈接庫函數(shù)的方法。您可以從官方文檔中探索所有可用的 Lua 標準庫模塊。
不會讓程序員感到困惑的錯誤處理策略
程序員應該正確處理程序中的錯誤。否則,特定程序可能會因嚴重錯誤而停止或產(chǎn)生無效輸出。使用面向 try-catch 的異常是現(xiàn)代軟件開發(fā)行業(yè)中最常用的錯誤處理策略。如果使用得當,使用 try-catch 異常是一個很好的策略,但異常通常會使代碼庫變得復雜。由于這個問題,Google C++ 代碼風格指南不建議使用異常,Golang 也沒有實現(xiàn)對使用基于 try-catch 的異常的支持。老式的類似 C 的錯誤代碼返回方法是簡化錯誤處理要求的方法。
Lua 不提供類似 Java 的基于 try-catch 的異常,但它提供了類似 Go 的基于錯誤代碼的簡化錯誤處理策略,您可以將其用作基于異常的錯誤處理方法。
看下面的例子:
function getresult(score)
if score > 100 then
error({code = 1002, msg = "Score shouldn't be higher than 100"})
elseif score >= 50 then
return 'P'
else
return 'F'
end
end
for _, v in pairs({20, 120, 60}) do
local ok, res = pcall(getresult, v)
if ok then
print("Result: " .. res)
else
print(string.format("Error [%s]: %s", res.code, res.msg))
end
end
默認情況下,Lua 會在錯誤時停止代碼執(zhí)行,因此如果您的程序嘗試對兩個包含字母的字符串執(zhí)行算術運算,程序將拋出錯誤并停止。pcall() 全局函數(shù)允許您捕獲這些錯誤并通過在受保護執(zhí)行模式下執(zhí)行代碼來繼續(xù)執(zhí)行代碼。
上面的代碼片段通過在 getresult() 函數(shù)實現(xiàn)中調用 error() 全局函數(shù)來引發(fā)錯誤。它通過使用 pcall() 調用 getresult() 函數(shù)來檢查錯誤狀態(tài)。因此,上面的代碼片段在屏幕上打印錯誤負載并繼續(xù)執(zhí)行,如下所示:
圖片
使用這種技術,我們可以簡單地進行錯誤處理,而不是像其他流行的現(xiàn)代語言那樣使用冗長的 try-catch 塊。如果拋出錯誤, pcall() 函數(shù)會動態(tài)設置第二個參數(shù)的錯誤表,否則,它會設置典型的返回值。
不使用任何特殊關鍵字的模塊系統(tǒng)
如果您使用過 JavaScript,您就會知道 JavaScript 模塊系統(tǒng)的復雜性。早些時候,Node.js 運行時使用 CommonJs 模塊系統(tǒng)。ECMAScript (ES) 標準引入了創(chuàng)建 JavaScript 模塊的新標準,然后 Node.js 開始支持 ES 模塊。因此,每個模塊系統(tǒng)都有不同的文件擴展名,即 .cjs 、 .mjs 、 .cts 等。標準 ES 模塊系統(tǒng)添加了三個新的文件擴展名JavaScript 的關鍵字/特殊標識符:export 、 import 和 as 。類似地,大多數(shù)流行的編程語言為模塊系統(tǒng)保留專用關鍵字。
Lua的模塊系統(tǒng)僅使用主要的 return 關鍵字和內置的 require() 全局函數(shù)。Lua 的模塊沒有實現(xiàn)任何保留的全局標識符,如 CommonJs 中的 module —— 它使用內置的表結構來定義模塊,如下例所示:
-- calc.lua
local calc = {}
function calc.add(a, b)
return a + b;
end
return calc
上面的代碼片段通過添加 add() 函數(shù)在 calc.lua 文件中定義了一個名為 calc 的模塊?,F(xiàn)在,您可以使用 require() 函數(shù)導入和調用模塊函數(shù):
-- main.lua
local calc = require("calc")
print(calc.add(10, 2)) -- 12
不涉及花哨的語法,也沒有引入新的專用關鍵字——這個最小的模塊系統(tǒng)可以在任何復雜的 Lua 項目中使用!
結論
在這個故事中,我們通過開發(fā)實用的 Lua 代碼示例來探索 Lua 腳本語言的簡單性。Lua 是一種對初學者友好的語言,具有最少的語法、少量的數(shù)據(jù)類型、只有一個內置的數(shù)據(jù)結構和一個簡單的標準庫。它也是一種功能齊全的語言,支持非搶占式多線程,并提供最小但功能齊全的標準庫。Lua 社區(qū)開發(fā)了一個 JIT 編譯器、一個托管數(shù)千個開源模塊的包管理器以及各種 C 庫的綁定,因此使用 Lua 構建生產(chǎn)軟件系統(tǒng)無疑是可能的。
然而,Lua 是一種被低估的語言,只有游戲開發(fā)者才知道。然而,它有潛力發(fā)展成為一種最小的動態(tài)類型腳本語言,并與 Python 和 Ruby 競爭。任何人都可以在幾分鐘內學會 Lua,因為它是有史以來最簡單、功能齊全的編程語言!
原文:https://levelup.gitconnected.com/lua-the-easiest-fully-featured-language-that-only-a-few-programmers-know-97476864bffc