学习 Lua 确实比学习 Python、Ruby 和 JavaScript 更容易!
创建一个简单易学的解释型编程语言并不难。我们只需要使用任何喜欢的编程语言编写一个解析器和语句运行器,基于简单的语言规范。为了提高性能,我们可以使用基于字节码的执行系统,而不是像 Bash 解释器那样直接执行解析后的源代码。任何人都可以创建简单易学的语言,但并非每种易学的语言都会成为一个有用的通用语言。例如,创建一个只支持基本算术运算的简单脚本语言并不会成为每个人都可以用来编程的有用语言。
大多数开发人员认为 Python、Ruby 和 JavaScript 是易学、简单且有用的语言。这些简单的语言提供了开发人员友好、高效、简单的语法,激励每个程序员使用它们来构建任何软件项目,并借助基于社区的库。毫无疑问,Python 提供了比 JavaScript 更简单的语法 - 对大多数程序员来说,编写 Python 代码感觉就像编写伪代码。Python 是有史以来最简单(但有用)的语言吗?
Lua 是一种动态类型、轻量级、可嵌入、功能齐全的通用语言,比 Python 更容易学习。大多数游戏开发人员了解 Lua 语言,因为它广泛用作嵌入式脚本语言,嵌入在基于 C/C++ 的游戏引擎中。然而,大多数非游戏开发人员不了解 Lua - 他们仍然认为 Python 是世界上最容易的语言。在这篇文章中,我们将探讨 Lua 提供的简单性。
编程语言具有不同的学习曲线。您可以更快地学习一些语言,因为它们具有较少的关键字、最小的语法和较少的独特核心概念。与此同时,如果一些语言引入超越传统理论编程概念的独特概念,对于新手来说,这些语言可能变得更加复杂。
Lua 是一种简单的语言,您可以利用您已有的计算机科学知识掌握它 - 您无需学习任何超出核心编程基础的独特内容就能成为 Lua 专家。
Lua 只有 22 个关键字、8 种数据类型和只有一种数据结构,您可以使用它来构建任何复杂的结构。如果您知道如何编写理论伪代码,您就可以在 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
看看上面递归阶乘数生成程序的简单性。它没有复杂化源代码的花哨语法 - 语言语法对大多数开发人员来说是不言自明的。Lua 使用 end
关键字定义控制块,类似于经典伪代码。它使用 ..
进行连接,--
作为单行注释的前缀。
您甚至可以编写一行式的 if 块如下:
function fact(n)
if n == 1 then return 1 end
return n * fact(n - 1)
end
Lua 提供了一个比 Python 更简单的数值 for 循环语法:
for i = 1, 10 do
print(i)
end
在几乎所有情况下,Lua 努力通过保持整体语言简单性来最大程度地重用现有语法,而不是引入新的语法。看看上面的数值 for 循环如何使用赋值运算符。有些语言看起来很简洁,但却有许多隐藏的概念和语法,因此即使开发人员可以在几分钟内开始使用这些语言,他们可能会花费数年时间掌握它们。
您可以在几秒钟内学会 Lua,并在几分钟内掌握它!
现代编程语言通常提供多种预先开发的数据结构,如数组、列表、映射、队列、向量、集合等,但在大多数程序中我们只使用少数数据结构。当特定编程语言添加新的数据结构时,可能会为每个结构引入新的语法,从而影响语言的最小设计,例如,Python 有三种初始化三种数据结构的语法:
type([1, 2]) # <class 'list'>
type((2, 5)) # <class 'tuple'>
type({"a": 10, "b": 20}) # <class 'dict'>
Lua 使用一种称为表结构的关联数组结构来处理所有内容。它让用户只需使用基于大括号的一种语法就可以创建数组、映射,以及任何东西:
local array = {1, 4, 10, 12}
array[1] = 10
print(array[1]) -- 10(Lua 中数组索引从 1 开始)
local map = {width = 200, height = 100}
map["width"] = 250
map.width = 350
print(map.width) -- 350
print(type(array), type(map)) -- table table
上面的代码片段使用表创建了 array
数组。当我们不使用关联键值对时,表实例可以作为具有数值索引的传统数组访问。Lua 允许您使用类似 C 结构初始化的赋值运算符创建映射。当您使用表结构创建映射时,您可以使用类似 JavaScript 的属性访问语法,就像上面的 map
变量所示。
上面的代码片段使用 local
关键字使这些变量成为局部变量,因为 Lua 是一种词法作用域简单语言。Lua 设计了有史以来最简单的语法来获取数组的长度:
print(#{1, 2, 5, 1}) -- 4
local arr = {1, 2}
print(#arr) -- 2
您可以使用 Lua 表实现任何复杂的数据结构,如 此 GitHub 仓库 中所示。使用基本 Lua 表结构开发数据结构可以提高您的数据结构和算法技能。以下故事解释了为什么每个开发人员都应该学习数据结构和算法概念:
每种最小语言可能并不提供现代开发人员友好、高效的语法和功能。例如,C 无疑是一种只有 32 个关键字的最小语言,但它并不为现代开发人员提供友好、高效的环境,因为它没有映射结构、动态列表、高效的字符串处理方法、自动内存管理(垃圾回收)、面向对象编程 特性、以及以提高生产力为重点的简写功能。
Lua 是一种最小语言,但它经过精心设计,以最简洁的方式回答开发人员在最小化设计方面的每个需求。Lua 允许您使用类似 Python 的现代方法迭代数组和映射:
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()
全局可迭代函数打印 vowels
和 scores
结构的内容,如下所示:
Lua 支持多重赋值和多返回值函数,作为一种友好的现代语言:
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 是一种多范式语言,因此它为函数式和面向对象风格提供了特性。例如,它允许您创建 lambda 函数如下:
function exec(func)
print("Running lambda...")
func()
end
exec(function() print("Lua") end) -- Lua
exec(function() end) -- (空函数)
Lua 并不像大多数面向行业的编程语言(如 C# 或 Java)提供许多内置的面向对象编程特性,但它提供了一种类似 Go 的最小类创建,没有内置继承特性:
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
表结构创建了一个类,并通过使用 setmetatable()
内置函数和 __index
元方法在表中附加了类属性和方法。您还可以通过使用原型系统和元表构建继承来实现继承。从官方文档了解更多关于元表的知识。
一个最小但功能强大的标准库Lua拥有一个精简但功能强大的预导入标准库,提供了数学函数、文件处理、操作系统函数、非抢占式多线程、调试、字符串操作、表操作以及与动态链接库通信的功能。Lua的标准库也是多范式的,这意味着你可以通过传递标识符调用标准库函数,或者将它们作为绑定对象的方法来调用。
例如,看看以下Lua代码片段如何调用字符串函数/方法:
local msg = "Lua"
print(string.lower(msg)) -- lua(使用函数式风格)
print(msg:lower()) -- lua(使用面向对象风格)
print(string.reverse(msg)) -- auL
print(string.sub(msg, 1, 2)) -- Lu
借助标准库中的面向对象风格支持,你可以高效地链式调用字符串方法,如下所示:
local msg = "Hello Lua"
print(msg:sub(7):lower():reverse()) -- aul
Lua没有实现正则表达式,因为它会影响Lua嵌入式程序的大小和Lua参考实现的复杂性,因此它提供了一种轻量级的类似正则表达式但最小化的模式匹配实现,如下示例所示:
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
典型的正则表达式实现需要编写超过4000行的代码,但Lua通过不到500行的代码实现了自己的类似正则表达式但轻量级的模式匹配解决方案:
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
模块导出操作系统级操作,并通过package
模块提供了调用动态链接库函数的方式。你可以从官方文档中探索所有可用的Lua标准库模块。
程序员应该正确处理程序中的错误。否则,特定程序可能由于关键错误而停止,或产生无效输出。在现代软件开发行业中,使用基于try-catch的异常是最常用的错误处理策略。如果使用得当,使用try-catch异常是一个不错的策略,但异常经常会使代码库复杂化。由于这个问题,Google C++代码风格指南不建议使用异常,而且Golang也不实现支持基于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在错误时会停止代码执行,因此如果你的程序尝试对包含字母的两个字符串执行算术运算,程序将抛出错误并停止。pcall()
全局函数允许你捕获这些错误,并通过在受保护的执行模式中执行代码来继续代码执行。
上面的代码片段通过在getresult()
函数实现中调用error()
全局函数来抛出错误。它通过使用pcall()
调用getresult()
函数来检查错误状态。因此,上面的代码片段在屏幕上打印错误负载并继续执行如下:
使用这种技术,我们可以简单地进行错误处理,而不是像其他流行的现代语言中那样使用冗长的try-catch块。pcall()
函数在抛出错误时为第二个参数动态设置错误表 - 否则,它设置典型的返回值。
如果你使用过JavaScript,你会了解JavaScript模块系统的复杂性。早期,Node.js运行时使用了CommonJs模块系统。ECMAScript(ES)标准引入了一个新标准来创建JavaScript模块,然后Node.js开始支持ES模块。因此,为每个模块系统引入了不同的文件扩展名,即.cjs
、.mjs
、.cts
等。标准ES模块系统为JavaScript添加了三个新关键字/特殊标识符:export
、import
和as
。同样,大多数流行的编程语言为模块系统保留了专用关键字。
Lua的模块系统只使用主要的return
关键字和内置的require()
全局函数。Lua的模块不实现任何保留的全局标识符,如CommonJs中的module
- 它使用内置的表结构来定义模块,如下示例所示:
-- calc.lua
local calc = {}
function calc.add(a, b)
return a + b;
end
return calc
上面的代码片段通过添加add()
函数在calc.lua
文件中定义了一个名为calc
的模块。现在,你可以使用require()
函数导入和调用模块函数:
-- main.lua
local calc = require("calc")
print(calc.add(10, 2)) -- 12
没有涉及花哨的语法,也没有引入新的专用关键字 - 这种简化的模块系统可以在任何复杂的Lua项目中使用!
在这个故事中,我们通过开发实用的Lua代码示例探索了Lua脚本语言的简单性。Lua是一种适合初学者的语言,具有最小的语法、少量的数据类型、仅一个内置数据结构和一个简单的标准库。它还是一种功能齐全的语言,支持非抢占式多线程,并提供了一个精简但功能齐全的标准库。Lua社区开发了一个JIT编译器、一个托管数千个开源模块的包管理器,以及各种C库的绑定,因此使用Lua构建生产软件系统是完全可能的。
然而,Lua是一种被低估的语言,只有游戏开发人员知道。然而,作为一种最小化的、动态类型的脚本语言,它有潜力与Python和Ruby竞争。任何人都可以在几分钟内学会Lua,因为它是有史以来最简单、功能最齐全的编程语言!