创建一种简单易学的解释型编程语言并非难事。我们只需根据简单的语言规范,使用任何喜欢的编程语言编写一个解析器和语句运行程序。为了提高性能,我们可以使用基于字节码的执行系统,而不是像 Bash 解释器那样直接执行解析后的源代码。任何人都可以创造出简单易学的语言,但每一种易学的语言都不会成为有用的通用语言。例如,创建一种只支持基本算术运算的简单脚本语言,并不会成为一种人人都能用来编程的有用语言。
大多数开发人员认为 Python、Ruby 和 JavaScript 是易于学习、简单且有用的语言。这些简单的语言提供了开发人员友好、高效、简单的语法,激励每个程序员在基于社区的库的帮助下使用它们来构建任何软件项目。毫无疑问,Python 提供了比 JavaScript 语言更简单的语法——对于大多数程序员来说,编写 Python 代码就像编写伪代码一样。Python 是有史以来最简单(但有用)的语言吗?
Lua 是一种动态类型、轻量级、可嵌入、功能齐全的通用语言,比 Python 更容易学习。大多数游戏开发人员都知道 Lua 语言,因为它广泛用作基于 C/C++ 的游戏引擎中的嵌入式脚本语言。然而,大多数非游戏开发者并不了解 Lua——他们仍然认为 Python 是世界上最简单的语言。在这个故事中,我们将探索 Lua 提供的简单性。
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 循环是如何使用赋值运算符的。有些语言看起来很简单,但它们有许多隐藏的概念和语法,因此开发人员可能需要花费数年时间来掌握它们,即使他们可以在几分钟内开始使用这些语言。
一切都只有一种数据结构
现代编程语言通常提供多种预开发的数据结构,如数组、列表、映射、队列、向量、集合等,但我们在大多数程序中只使用少数几种数据结构。当一种特定的编程语言增加了新的数据结构时,它可能会通过影响语言的最小设计来为每种结构引入一种新的语法,例如,Python 有三种初始化三种数据结构的语法:
type([1, 2]) #
type((2, 5)) #
type({"a": 10, "b": 20}) #
Lua 仅使用称为表结构的关联数组结构来处理所有事情。它允许用户仅使用一种基于大括号的语法来创建数组、映射和任何其他内容:
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
上面的代码片段创建了带有表的 array 数组。当我们不使用关联键值对时,表实例可以作为具有数字索引的传统数组进行访问。Lua 允许您使用带有类似于 C 结构体初始化的赋值运算符的表来创建映射。当您使用表结构创建地图时,您可以使用类似 JavaScript 的属性访问语法,如上面的 map 变量所示。
上面的代码片段使用 local 关键字使这些变量成为本地变量,因为 Lua 是一种词法范围的简单语言。Lua 拥有有史以来最简单的语法来获取数组的长度:
print(#{1, 2, 5, 1}) -- 4
local arr = {1, 2}
print(#arr) -- 2
为现代开发人员提供友好、高效的环境
每种极简语言都可能无法为现代开发人员提供友好、高效的语法和功能。例如,C 语言无疑是一种只有 32 个关键字的极简语言,但它并没有为现代开发人员提供友好、高效的环境,因为它没有映射结构、动态列表、高效的字符串处理方法、自动内存管理(垃圾回收)、基于 OOP 的功能以及注重生产力的速记功能。
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) -- (empty function)
Lua 不像 C# 或 Java 等大多数以行业为中心的编程语言那样提供那么多内置的 OOP 功能,但它提供了类似 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 (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 没有实现 Regex,因为它会影响 Lua 嵌入程序的大小和 Lua 参考实现的复杂性,因此它提供了一个轻量级的类似 Regex 的但最小模式匹配的实现,如以下示例所示:
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 实现需要编写 4000 多行代码,但 Lua 用不到 500 行代码实现了自己的类似 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 模块导出操作系统级操作,并提供一种通过 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,因为它是有史以来最简单、功能齐全的编程语言!
原文:https://levelup.gitconnected.com/lua-the-easiest-fully-featured-language-that-only-a-few-programmers-know-97476864bffc