标签为“Lua”的页面如下
【30天Lua重拾筆記】系列目錄
本系列文章為 第12屆iT邦幫忙鐵人賽 參賽文章。
你可以同時於iT邦幫忙找到本系列目錄。
最全面的Lua入門學習…筆記草稿?No, No, No, No, No 在30天要所有東西提到貌似是不太可能了,但這將會是一個由淺入深的Lua參考筆記。會竟可能涵蓋所有Lua相關核心內容。
Lua非常小,有經驗的人甚至可以在幾小時內熟悉Lua核心基礎內容、幾周內使用進階功能。並且透過輕而小的Lua,或許可以從另一角度重視其他程式語言。是C、Lisp以外,我最為推薦學習的程式語言之一。
本系列包含內容:認識Lua、基礎型別、控制流程、進階概念、範例嵌入C/Java。從頭帶你了解Lua怎麼回事。
系列目錄
【30天Lua重拾筆記35】完賽感想與延伸閱讀
完賽感言
這系列文章在我3月當兵時就開始在規劃了,可是寫出來也還是和原本預計的差了蠻多的,看看我一開始預計撰寫的內容…
起初,我更是想說Lua這麼小,那應該可以非常完整的說明完所有部份吧!但越後面開始規劃每日的文章才發現…絕對會超過30篇。
而且更多時候是寫到一半,發現這邊不得不提到一下之後才會說明的部份。又或者是邊查資料邊寫,結果與預計寫的方向差了不少。又有些時候,因為對Lua已經有一些基礎了解,手打字追不上想寫的思路,或是大思路雖然有了,卻一直在某些地方腦袋打結…
這系列文章,有很多篇也比我預計寫的內容詳細了許多,比起最初可能只是各方面都提到一點點的筆記,儼然已經變成超過我預期內容的一些探討。自己撰寫收穫也頗多的,也還好目前並不算忙碌,才可以這樣做。若要給未來自己一些建議:
- 不管想到的是否完整、好壞,都可以先寫下來。
這次有許多已經先寫下來的小紀錄,最後都變成很常的文章或範例程式。有更多更是在其他文章撰寫下,也不停的修改。 - 不用多,先寫下小小的東西就好。當做種下一顆未來的種子,當未來有需要使用時,再來灌溉、發芽。
- 還是自己寫過、想過的東西,在未來更容以理解。
最後,感謝閱讀完本系列的文章的各位,本文章原本想要深入淺出,但中有諸多未能提及之處,只能在最後分享一些資源。
延伸閱讀
【30天Lua重拾筆記34】番外篇: Fengari - 一個JS實現的Lua,運行Lua在瀏覽器內吧!
幾年前關注過Moonshine和lua.vm.js,不過這兩個項目貌似沒什麼在更新了。Fengari這個這次到又是讓我為之一亮
Lua的實現真蠻多樣的,光是想讓Lua運行在瀏覽器就有不少,像是Moonshine、lua.vm.js、Starlight。有些使用JS;也有些利用了WASM、emscripten。Fengari是屬於前者的實現,是JS實現的版本。其除了可以在瀏覽器執行外,也提供了基於Node.js的執行器。這是一個蠻新興的項目,整體設計粗淺看來也相當不錯而且完整,今天會略微介紹一下,但建議可以先閱讀閱讀為什麼我們使用JS重寫了Lua?(英文)
Fengari是希臘文「月亮」的意思
於瀏覽器執行Lua
【30天Lua重拾筆記33】Java + Lua計算機
這是我前幾年作為學習/練習的例子。
看過與C交互後,接著來看看一個更實際應用的例子。不過不用C,來用Java。
為甚麼呢?Java自帶一個跨平台的視窗開發套組,本身也有豐富的函式庫可用,第三方函式庫也眾多,作為宿主語言是蠻好的標的。不過不直接使用原本的Lua,需要使用LuaJ。
其實最初只是因為Android App使用Java開發。而當時Android Studio編譯APK實在太慢,才會有為啥不能先用其他方式寫邏輯、開發,為啥不先嵌入一個腳本語言?才去做的一個練習,之後我使用的套件也確實有用於Android App開發上。
是的,如果你是正在開發Android,不管是以前主要用Java還是後來的Kotlin,今天應該都還是可以使用LuaJ。
LuaJ已經停滯好一段時間了,本次範例的為GitHub上另一個分支。基本上遵循Lua 5.2的規則,也就是本系列有部份是Lua 5.3、5.4的語法並不能使用於之。
目標
本次範例主要目標:一個含有GUI的簡易計算機。其包含:
- Java提供GUI界面,和部份功能。
- Lua處理邏輯。
- 一個擴展方法,能夠改變成品行為。
使用Lua撰寫邏輯
使用Lua的好處之一,在不確定要以和總平台開發前,是可以先使Lua撰寫邏輯,最後在嵌入到某個平台或框架。
【30天Lua重拾筆記32】進階議題: LuaRocks & LuaDist
LuaRocks
LuaRocks是類似npm、pip這樣的套件管理工具,你可以在上頭找到近4000個別人已經寫好的模組。
下載/安裝LuaRocks
在示例前,你需要先下載安裝好LuaRocks,若要下載其他版本,可以在這個頁面尋找看看。
在Linux上,你只需要將其解壓縮於可執行路徑底下即可。Windows可以參考官方說明。
使用LuaRocks
初始化環境
你可以不用做這一步。略過這一不的話,之後安裝得套件會存在於全局裡。有這樣的作法,主要是用於開發其他套件時使用。
mkdir example
cd example
luarocks init
搜尋套件
【30天Lua重拾筆記31】進階議題: 記憶體回收&弱表
TL;DR:
不要去修改預設值,除非你知道在做什麼
Lua會自己做記憶體回收,絕大多數時候不必為記憶體分配、管理而操心,而且通常它做的很好。但如果真的因為記憶體回收而影響到程式效率的執行,且你確定你有足夠的記憶體,你可以暫停讓Lua執行記憶體回收的操作:
停止收集記憶體垃圾
collectgarbage("stop")
collectgarbage("isrunning") --> false
或者,如果你是嵌入在C,可能會更傾向使用:
lua_gc(L, LUA_GCSTOP)
收集記憶體垃圾
如果要恢復,也只要:
collectgarbage("restart")
collectgarbage("isrunning") --> true
或是
【30天Lua重拾筆記30】進階議題: 與C交互(+Python)
Hello, Lua & C
現在,我們來嘗試從C去執行一個Lua程式,Lua程式就用最簡單的Hello,並命名為hello.lua
print "Hello"
然後來寫C程式 – hello_C.c
。
引入標頭檔
需要下載含有標頭檔和函式庫的版本
#include "lua.h"
#include "lauxlib.h"
建立Lua虛擬機
// new a lua VM
lua_State *L = luaL_newstate();
打開預設的所有函式庫
通常而言,不會全部開啟所有功能。
這只是範例,讓hello.lua
檔案擁有所有能力。
// open all libraries
luaL_openlibs(L);
執行Lua檔案
【30天Lua重拾筆記29】進階議題: 物件導向程式設計
與ECMAScript相同,採用原形設計的物件導向。你可以與7天搞懂JS進階議題相互服用,可能會有意想不到的效果。
模擬封裝
Lua並沒有直接保護內部資料的方法,你可能會需要使用 閉包 、 弱表 、 metatable 等來達成條件。
但今天只討論封裝裡最簡單的概念–集成,也是物件的基礎:將相關的物件、方法關連到一個結構裡面。沒錯,就是 table
,在本文中使用物件(object)一詞基本可視為同義。
Lua的
table
也確實很像是JS裡的Object
,但可能更像是Map
(一個ES6後出現的新類別)。
最基礎的資料結構概念是雜湊表。
建立物件1
建立物件(object
)就是建立表(table
),非常簡單:
coco = {
name = "桐生ココ",
age = 3501,
birth = {month = 6, day = 17},
slogan = "good morning mother f**ker",
say = function (self)
print(self.name .. ": " .. self.slogan)
end
}
coco:say() --> 桐生ココ: good morning mother f**ker
工廠模式
但我們總不能每次都這樣直接建立物件,或許可以由一個加工工廠(函數)來處理?
【30天Lua重拾筆記28】進階議題: Meta Programming
Meta Programming / 元程式設計
元程式設計(英語:Metaprogramming),又譯超程式設計,是指某類電腦程式的編寫,這類電腦程式編寫或者操縱其它程式(或者自身)作為它們的資料,或者在執行時完成部分本應在編譯時完成的工作。多數情況下,與手工編寫全部代碼相比,程式設計師可以獲得更高的工作效率,或者給與程式更大的靈活度去處理新的情形而無需重新編譯。 – 維基百科
簡單說,元程式設計,就是讓「程式能夠編寫程式」,改變程式運行的部份行為。Lua本身具有部份如此的能力,舉例來說,如果想要建立一組變數A-Z
,或許正常會這樣子寫:
A = 1
B = 2
C = 3
-- ....
Z = 26
但Lua可以有更聰明(hacking)的寫法:
for i=65, 65+25 do -- ASCII Code of A is 65
print(string.char(i))
_ENV[string.char(i)] = i - 64 -- 1 to 26
end
還記得_ENV的作用嗎?
【30天Lua重拾筆記27】進階議題: debug
Lua本身並沒有獨立的debugger相關工具,但他有一個強大的內置套件— debug。
打印調錯訊息traceback
debug = require "debug"
do
local ten = 0
function div10(n)
print(debug.traceback()) -- 打印調錯訊息
return n / ten
end
end
更新變數值
透過直接觀看函式內容,可以知道這個函式div10
並不符合預期。
do
local ten = 0
function div10(n)
return n / ten
end
end
所以需要改變ten
值,但這次不直接修改程式原始碼。我們得先了解怎麼取得閉包變數ten
:
【30天Lua重拾筆記26】進階議題: 錯誤處理
作為一個寄宿型的嵌入式語言,Lua設計更傾向由宿主語言(通常是C)處理錯誤。
但是可以在保護模式下,執行函式,並檢查函式是否執行成功。
很像是Go語言。這就是Lua的錯誤處理基本方法。
錯誤處理
作為一個寄宿型的嵌入式語言,Lua設計更傾向由宿主語言(通常是C)處理錯誤。
num = 10
str = "string"
print('Hello, Lua')
result = num / str -- Error: attempt to div a 'number' with a 'string'
print('here will not show, because error hapend before')
一般來說,並不傾向於Lua處理最後,因為數字與字串相除,所引發的錯誤。這個錯誤會持續引發至宿主語言,由宿主語言進行錯誤處裡。
【30天Lua重拾筆記25】進階議題: 模組化
Lua並沒有完整的模組系統,更多的是依賴模組開發者的設計。在Lua 5.1曾經有module()
的函數可用,但於Lua 5.2已經被移除。更多的需要使用_G
、_ENV
來使用,相關說明可以參考全局表與環境表。
module (name [, ...])
在require()
之前,需要先說一下如何載入程式檔案並執行。
載入檔案
對於模組化設計,最重要的一點是如何切割程式為不同檔案,接著就是如何串連不同檔案。在之前介紹過load
,Lua提供了另一個類似的方法loadfile
。
示例
首先先建立個 hello.lua 檔案,簡單就好:
print "Hello, World"
然後才是主程式 loadfile_hello_example.lua :
hello = loadfile("./hello.lua")
hello() --> Output: Hello, World
如果你分別於REPL環境打入這兩行指令,你會發現第一行指令並沒有任何輸出結果,只得到一個hello函數。只有在第二行執行函式時,才真正執行hello.lua程式檔案的內容。
執行檔案
【30天Lua重拾筆記24】中級議題: coroutine
coroutine
Lua提供coroutine
的函式庫,使其有能力編寫不同模式的程式。
thread create
你可以透過coroutine.create()
建立一個thread
。
t1 = coroutine.create(function() print("Hello, World") end)
print(type(t1)) -- Output: thread
Lua並不是多執行緒的,其thread
是輕量的。儘管Lua本身沒有異步(async)的寫法,但可以創造出異步的寫法。相對而言,值執行順序是明確許多的。
thread running
可以透過coroutine.resume()
去觸發一個thread
的執行:
coroutine.resume(t1) -- Output: Hello, World
傳入參數
可以對一個剛建立好的thread
傳入參數。
function hello(name)
while true do
coroutine.yield("Hello, " .. name)
end
end
t1 = coroutine.create(hello)
print(coroutine.status(t1)) -- suspended
print(coroutine.resume(t1, "Bob")) -- Output: Hello, Bob
print(coroutine.status(t1)) -- suspended
print(coroutine.resume(t1, "World")) -- Output: Hello, Bob
thread close
【30天Lua重拾筆記23】中級議題: 閉包
變數的查找
對於一個變數,Lua會先嘗試從當前詞法環境(Lexical)尋找,再從當前環境中尋找(
_ENV
)。
那的對於區塊變數呢??
function parentFunction()
local L1 = 100
local function childFunction()
print("here is child")
end
return childFunction
end
f1 = parentFunction(100)
f1() -- Output: here is child
上例中,區塊變數L1
沒有任何人可以存取的到,簡直消失在了時空夾縫之中。
詞法環境(Lexical)
詞法環境(Lexical)指的是程式編寫當下,執行區塊由內而外,可以見到的範圍。
隱藏區塊變數
在Lua變數預設值為nil
。透過查找規則,可以暫時隱藏一切區塊變數。
【30天Lua重拾筆記22】中級議題: 全局表(_G)、環境表(_ENV)
_G
和_ENV
在Lua有兩個特殊變量–_G
和_ENV
,其分別表示全局環境和當前環境。_G
在與C交互時,另有作用。但大致上你可以將兩者視為相同。實際上,在Lua環境建立之初,兩著也確實是相同的:
print(_G == _ENV) -- => Output: true
先前說過,Lua只有表(table
)這個複合結構。而_G
和_ENV
也是table
結構。_ENV
中包含一個_G
的key
,其值指向_G
,而起出_G
就是_ENV
。像是一個咬尾蛇(Ouroboros)
print(_ENV._G == _ENV) -- => Output: true
print(_G == _ENV) -- => Output: true
print(_G._G == _ENV) -- => Output: true
print(_G._G == _G) -- => Output: true
變數查找
對於一個變數,Lua會先嘗試從當前詞法環境(Lexical)尋找,在從當前環境中尋找(_ENV
)。這意味著你可以準備一個乾淨的執行環境執行函數。
【30天Lua重拾筆記21】基礎3: 再看pairs, ipairs
ipairs()
的行為
iparis會嘗試從索引1開始迭代表(陣列),直到其值為nil
。所以很像是:
arr = {1,2,3,4,5}
function my_ipairs(t --[[table]])
local i = 1
while t[i] ~= nil do
print(i, t[i])
i = i + 1
end
end
my_ipairs(arr)
1 1
2 2
3 3
4 4
5 5
也因此其如果有斷值,會在一半中止:
arr = {1,2,nil, 4, 5}
my_ipairs(arr)
1 1
2 2
pairs()
和next()
【30天Lua重拾筆記20】基礎3: 複合結構 - table
Lua只有一個原生的複合結構 – table
。實際上陣列是table
的特例。
陣列是table
的特例
arr = {1,2,3,4}
print(type(arr)) --> table
建立表(table
)
table
對應於:
程式語言 | 型別 |
---|---|
python | dict |
js | object or Map |
這個結構幾乎是現代高階語言必備的基礎。其又稱作hash-table
,是一個鍵值對(key/value)的結構。在Lua之中,其key可以是除了nil
和NaN
(Not a Number)以外的任何型別。
key值值域
obj = {} -- 建立一個空表
obj[1] = 1 -- 整數是合法的key值
obj[1.0] = 2 -- 浮點數是合法的key值
obj["string"] = 1 -- 字串是合法的key值
obj[math.huge] = 1 -- inf是合法的key值
--[[
要注意的是 obj[1] 和 obj[1.0]相同
其obj[1]和obj[1.0]最終值為2
--]]
----------------
print(obj[nil]) --> nil
obj[nil] = 1 --> Error: nil不是合法的key值,儘管取值不會出錯
print(obj[0/0]) --> nil
print(0/0) --> -nan
obj[0/0] = 1 --> Error: NaN不是合法的key值
value值值域
其value可以是nil
以外的值。
nil表示該key不存在,亦可視為刪除該key
obj[1] = nil -- 刪除`obj[1]`
print(obj[1]) --> nil --表示obj[1]不存在
【30天Lua重拾筆記19】基礎3: 陣列從1開始
建立陣列
關於陣列,其實也已經看過了。不過其實陣列還有兩個祕密,一個今天會揭露,另一個等等明天。
要建立一個陣列很簡單,很像C語言,只是把中括號[]
改成大括號{}
,像是:
programming_language = {
"C",
"C#",
"C++",
"Java",
"Swift",
"Python",
"Haskell",
}
陣列取值
陣列從1開始
與C語言一樣,使用下標運算取陣列之中的值, 要注意的是,Lua陣列從1開始
print(programming_language[1]) -- => Output: "C"
陣列長度
可以使用長度運算#
,取得陣列長度:
print(#programming_language) -- => Output: 7
迭代陣列
知道陣列取值方式,也知道陣列長度後,就可以來迭代陣列:
一般for迴圈方法
for i=1, #programming_language, 1 do
print(i, #programming_language[i], programming_language[i])
end
看我小巧思,每個值的長度,正好與其對應的索引值相同。
for-in + ipairs() 方法
可以寫的更簡單點:
【30天Lua重拾筆記18】基礎2: 應該知道的(總集+補充)
關於變數
- 值(value)有型別;變數(varible)沒有
- 基礎型別一共有8個
nil
boolean
number
string
function
userdata
thread
table
- table是唯一的複合型別(之後會提到)
- Lua 5.4之後,可以為變數標記屬性。Lua 5.4支援
const
和close
兩種屬性 - 變數預設是全局變數(實際上與特殊變數
_ENV
和_G
有關,之後會提到) - 變數值型別會自動轉型,但不該依賴
數字
- 整數和浮點數會自動轉換
- 整數有範圍,有溢位問題
- 浮點數也有精度丟失的問題
布林
- 只有
nil
和false
為假,其他為真 0
、空字串、空表亦為真
控制結構
- 有label &
goto
- label只屬於當前函式區塊
- 使用
elseif
,而非else if
(這點我覺得C就設計的比較漂亮)- 嵌套使用
if
(else if
),記得補上end
- 嵌套使用
- 非強求,但最好做好排版
【30天Lua重拾筆記17】基礎2: pcall, xpcall, load (eval, exec, apply)
eval / load
作為一個直譯的環境,幾乎一定會有一個與eval
等價的能力,不過在Lua叫做load
,與其他程式相同,這個功能是強大而應該小心使用的。下方程式會新建一個全局變數g1
,使這個宣告過程原本是一段字串。
load([[
g1 = 1 -- create a global variable g1
]])()
print(g1) -- => Output: 1
要注意的是,load
不回直接執行,其實其返回一個包裝函式:
f = load[[g2 = 2]]
print(type(f)) -- => function
print(g2) -- => Output: nil
f()
print(g2) -- => Output: 2
ECMAScript
類似的JS也有。
不過相較於JS,Lua其實有更安全的作法(以後會提到)。
eval('var g1 = 1')
console.log(g1) // => Output: 1
Python
Python有兩個函式做類似事情–eval
和exec
。不過eval
雖有返回值,但無法執行語句,只能執行表達式。所以本例中只能使用exec
。exec
永遠回傳None
,比起eval
更加強大。
【30天Lua重拾筆記16】基礎2: 多值返回&具名參數
回傳多值/多值返回
Lua函數可以返回多值。在我看來,這個特性是特殊的,只有少數語言真正做到多值返回。什麼意思?這表示在接收一個函數的返回值,可以輕易忽略掉主要值以外的結果。這些結果一般用於輔助,絕大多數時候,我們不關心、不需要,但有時候非常有作用。
先來看看Python裡的dict.get:
_d = {}
_v = _d.get("key", "value")
print(_v) # => Output: value
大多數時候,並不關心_v
的值是否真的從_d
來的,還是其實是個預設值。如果我們想要確定,那就要在多寫一次:
found = "key" in _d
我們可以替Lua寫一個相似的函數,但其還多返回一個用於解釋是否是有找到的值:
function get(dict, key, default)
if dict[key] ~= nil then
return dict[key], true
else
return default, false
end
end
其實可以在簡化一些:
function get(dict, key, default)
return (dict[key] or default), dict[key] ~= nil
end
這麼一來,平常可以這樣用:
【30天Lua重拾筆記15】基礎2: Label and Goto
Label & goto
這是一個強大的工具,要寫的漂亮並不容易,許多語言禁止了他。
Lua保有他。他很靈活,但你也應該慎重考慮是否真的應該使用。
而我認為,開發人員應該要是自由的,這才有創造的價值,這才能體現思考的藝術。就如同松本行弘說的:「語言體現思考的價值」
如果你的想法最初就是這樣想的,就先寫下來吧!「童子軍原則」別忘了逐漸改得更健壯。
Lua保有C/C++裡,goto
的能力,可以跳轉到函數區塊內任意可見的標籤(Label)。標籤的形式由兩個冒號夾成:
:: <Label Name> ::
跳出多層迴圈
BTW, Python 認為如果你需要使用到這個功能,表示你應該在拆分程式碼。
有了這強大的能力可以做啥?可以學像ES6那樣跳出外部迴圈:
九九乘法表只計算到6x4
(()=>{
outer:
for(let j = 2; j <= 9; j++){
let line_output = ""
for(let i = 1; i <= 9; i++){
line_output += `${j}x${i}=${j*i},\t`
if(i > 3 && j > 5){
console.log(line_output)
break outer
}
}
console.log(line_output)
}
})()
【30天Lua重拾筆記14】基礎2: 控制-while、repeat迴圈
print("鐵人賽開始")
for day=1,30,1 do
print("第" .. day .. "天: 每天寫文章")
end
print("鐵人賽結束")
for
迴圈適合用於次數明確的情況。例如鐵人賽30天發文不間斷,每天發一篇,發完30完賽。向這樣知道進步條件(每天一篇),中止條件(30篇),就非常適合用 for
迴圈。
Repeat/until迴圈
但有時候只知道中止條件,過程並不確定。像是在第n天,就寫n篇文章來囤稿,對我而言會是幾天完賽呢?
print("鐵人賽開始")
day = 1 -- 第幾天
completed = 0 -- 完成篇數
repeat
completed = completed + day
print("第" .. day .. "天: 每天寫文章,完成"..completed.."篇")
day = day + 1
until completed > 30
print("鐵人賽結束,完成篇數:"..completed) -- Output: 鐵人賽結束,完成篇數:36 -- (1+8)*8/2
鐵人賽開始
第1天: 每天寫文章,完成1篇
第2天: 每天寫文章,完成3篇
第3天: 每天寫文章,完成6篇
第4天: 每天寫文章,完成10篇
第5天: 每天寫文章,完成15篇
第6天: 每天寫文章,完成21篇
第7天: 每天寫文章,完成28篇
第8天: 每天寫文章,完成36篇
鐵人賽結束,完成篇數:36
需要每天寫文章,直到累積的文章超過30篇,也就完賽了。如果在第n天寫n篇,也只需要8天就完賽。
While迴圈
【30天Lua重拾筆記13】基礎2: 控制-For迴圈
相較於if
,Lua的for
迴圈有兩種,或說是三種。
進步的for迴圈
印出1-10:
for i = 1, 10, 1 do
print(i)
end
很類似C語言
#include <stdio.h>
int main(void){
int i = 0;
for(i = 1; i <= 10; i++){
printf("%d\n", i);
}
}
不同的是以逗點代替分號,中止條件相同時仍會在執行一次,進步(step)使用表達是,會自動相加並重新賦予值。
了解後也可來寫成10到1的程式:
for i = 10, 1, -1 do
print(i)
end
for-do
也是一個區塊,於一開始宣告的變數僅do-end
內部可見。這表示下方寫法中,進步條件指的是外部的i
,其值為1
。結果與輸出1-10相同。
i = 1
for i = 1, 10, i do
print(i)
end
pairs & ipairs初探 - for-in迴圈
for-in
迴圈用於迭代陣列(array
)或表格(table
)時。
Lua是沒有array型態的(還記得8種基礎型別嗎),關於這點之後會在說明。
至於什麼是table
?可以想像成是ES6裡的object
,或者更接近於Map
可以分別用ipairs
和pairs
來處理:
迭代陣列
【30天Lua重拾筆記12】基礎2: 控制 - 條件
分支條件控制 - if/elseif/else
Lua的分支控制條件就僅有這麼一組:if-then
/elseif-then
/else
/end
和其他語言一樣,elseif
可以出現多次。你想用else if
寫成巢狀也沒人會管你。他們兩個寫起來也真的蠻像的,除了你可能要多寫幾次end
:
if true then
print("if block")
elseif true then
print("elseif block")
else
print("else")
end
if true then
print("if block")
else if true then
print("elseif block")
else
print("else")
end
end
注意到最後有兩個end
。實際上我試故意將他這樣排版的,這樣兩個看起來比較像。
通常需要使用到巢狀分支,還是好好縮排比較好。
switch
雖然只有if
蠻簡單的,但有時候會想要使用switch
這類結構。沒事,可以模擬:
option = "one"
switch = {
["one"] = function () print "run one" end,
["tow"] = function () print "run two" end,
}
switch[option]() -- => run one
【30天Lua重拾筆記11】基礎1: 註釋
基礎2: 註釋
--[[
{
author = "lagagain",
date = 20200904,
title = "Comments of Lua code",
}
--]]
註釋是一段永遠不會被執行的區塊。你可以在註釋區塊隨意寫任何東西,而不必遵守任何Lua的語法規則。但更好的,註釋應該是作為程式碼的部份補充。至今為止的幾天,其實也過不少註釋。像是:
------------------
阿勒? 😲 這不是分隔線嗎?
單行註釋
是的,最基本的單行註釋以 --
開頭,直至行尾都會被視為註釋。也看過:
do
local a = 1
local b = 2
local sum = a + b -- sum = 1 + 2 => 3
end
或是
print("Hello, World") -- Output: Hello, World
本系列會部份以這樣方式去說明程式碼,程式碼的執行結果。不過在寫註釋時,最好是明確的補充程式碼的意義,像是:
【30天Lua重拾筆記10】基礎1: 類型 - 布林和nil
nil
nil
是Lua裡的一個特殊值,代表什麼也沒有。其型別也是nil
type(nil) -- => nil
布林
布林值只有true
和false
真值
只要不是nil
或是false
都為真, 包含0、空表、空字串 。
這與目前多數主流語言不同,需要特別注意!
【30天Lua重拾筆記09】基礎1: 類型 - 函數
函數 宣告
函數可以使用function
來做宣告,並以end
結束。
function hello()
print("Hello, World")
end
實際上這是Lua的語法糖,上面相當於:
hello = function()
print("Hello, World")
end
函數也是第一公民
在Lua,函數是第一公民,是基本型別,可以榜定於變數,也可以作為參數傳遞,或是回傳 值。
function genFun()
return function()
print("Hello, World")
end
end
function callFun(f)
f()
end
local _f = genFun() -- # get a function
callFun(_f) -- print("Hello, World")
函數、區域變數與閉包
函數同時也是一個區塊,可以保有區域環境變數,這樣的特性可以用來做閉包。
【30天Lua重拾筆記08】基礎1: 類型 - 字串
關於字串
與Python相同,字串是不可變得。但Lua字串於內部表示時,完全採用8-bits表示,包含0(\0
)。這也是為什麼在基礎1: 變數一篇,會特別說明\u{1F603}
的使用。
所以字串如果取其長度,可能和你想像的不一樣:
-- note: with UTF-8
print(string.len("我")) -- equal print(#"我") => 3
不過Lua是認得UTF-8的,其本身可以協助不支援UTF-8的程式語言(ex: C語言)。
-- note: with UTF-8
print(utf8.len("我")) -- => 1
C90包含<wchar.h>; C11有<uchar.h>。
儘管如此,不表示就是UTF-8寬字元(Wide character) 是電腦抽象術語(沒有規定具體實現細節),表示比8位元字元還寬的資料類型。不同於Unicode。 – 維基百科
因為Lua認得UTF-8,所以其原始碼最好以UTF-8儲存,否則可能執行會與想像不同。
字串的表示式
要表示字串在Lua有多種方式。
字面字串
簡單表示
【30天Lua重拾筆記07】基礎1: 類型 - 數字
整數與小數
數字(number)是Lua的基礎型別之一。Lua會自動判斷是整數還是小數,會自動轉換,無明確分界。
1.0 == 1
list = {"Bob", "Lua", "Luna", "Selene"}
list[1] == list[1.0]
type(1.0) -- => number
type(1) -- => number
雖然會自動轉換,不代表沒有區別
math.type(1.0) -- => float
math.type(1) -- => integer
溢位(overflow)
Lua 5.4使用64位元的整數和浮點數(雙精度浮點數double)。
math.type(9223372036854775807) -- => integer
math.type(9223372036854775808) -- => float
這表示Lua還是有可能溢位
9223372036854775807 + 1 -- => -9223372036854775808
精度丟失
浮點數的表示也有極限,可能會有精度丟失的問題
【30天Lua重拾筆記06】基礎1: 變數
變數名稱
Lua的變數名稱可以是底線(_
)或是任意字母([a-zA-Z]
)開頭,不能是數字或其他字元。之後的組成可以包含數字([0-9]
)、字母([a-zA-Z]
)或底線,並且是大小寫敏感,abc
、Abc
、ABC
可以作為三個不同的變數。
Lua作為嵌入型語言,使用最基本的ascii code。儘管其本帶有utf8函式庫,也可能有部份實現允許其他字元作為變數名稱,但不建議這樣做,並且在使用到非ascii字元時,最好以\u{1F603}
這樣方式來寫。像是:
print("\u{1F603}")
print('😃')
上面兩著等價,或是下面兩者亦相同。
print("\u{4f60}\u{597d}\u{ff0c}\u{4e16}\u{754c}\u{ff01}")
print("你好,世界!")
當然其實Lua是了解utf-8的,本系列也不會以上述方式撰寫,所以請確定儲存的程式檔案是以utf-8儲存,否則執行結果可能不如預期。
此外,Lua的底線開頭的變數,和Python一樣有時也代表一些意義,這會在說道metatable和物件導向程式設計時說明。
變數可見範圍
與多數程式語言一樣,分有全局變數和局部變數。不同的是,預設變數可見範圍是全局的(類似JS的var變數)。
g1 = 1
function f()
print(g1)
g2 = 2
end
f()
print(g2)
【30天Lua重拾筆記05】基礎1: 程式區塊(block、chunk)、排版
Lua的關鍵字
Lua的關鍵字並不多,就只有這麼幾個而已:
and | break | do | else | elseif | end |
false | for | function | goto | if | in |
local | nil | not | or | repeat | return |
then | true | until | while |
區塊(block)
有一些關鍵字會組合成程式區塊(block)。像是:
funciton
-end
do
-end
if-then
-end
while-do
-end
repeat
-until
夾於這些關鍵字中間的程式碼會成為一個環境區塊。
在區塊環境內,可以保有一些局部變數:
do
local name = "World"
print("Hello, "..name)
end
print(name)
Hello, World
nil
chunk
【30天Lua重拾筆記04】基礎1: Hello, Lua!
假設你已經選擇好並安裝 Lua的實現,且也準備好開發環境。使用過lua -v
沒問題後,就可以來試試看今天的入門示範程式。
你不必馬上了解今天的所有內容,將來都會說明到。今天只是快速的看看Lua程式長什麼樣子。
你可以執行lua
,在REPL交互式環境執行範例程式碼,也可以另存檔案,並用lua file.lua
執行看看。
第一個程式 - Hello, World
第一個程式?當然從打印出Hello, World開始。
Hello, World是指在電腦螢幕顯示「Hello, World!」(你好,世界!)字串的電腦程式。相關的程式通常都是每種電腦程式語言最基本、最簡單的程式,也會用作示範一個程式語言如何運作。同時它亦可以用來確認一個程式語言的編譯器、程式開發環境及運行環境是否已經安裝妥當。 – 維基百科
print "Hello, World"
Hello, World
不過這也太簡單了,來換個方式試試。
給個名字 - Hello, Lua
【30天Lua重拾筆記03】開發環境配置
開發環境配置
接著,來配置一下開發環境。主要會介紹三個開發環境,當然你想使用純文本編輯器也可以,我就是使用Emacs。
我會建議初學的人只使用代碼高亮的功能就好,最多…就使用到查找文件說明。 儘管今天介紹的配置都包含自動補全、格式美化、定義跳轉等等功能。但初學的人應該更關注在於其語法。(Lua語法也蠻自由的就是)
ZeroBrane
ZeroBrane是一個相當完整的Lua IDE,你幾乎可以直接下載下來使用。
其已經包含數個Lua版本,並可以輕易切換。
他也包含許多IDE應該要有的功能:
- 跳轉到函式定義
- 自動補全
- 除錯器
【30天Lua重拾筆記02】Lua的實現與選擇
Lua的實現與選擇
Lua的意思是葡萄牙文的「月亮」,其LOGO和其他相關也多與月亮有關。在開始使用學習Lua之前,比須先了解Lua的幾個版本與實際實現。
就像Python 2和Python 3有很大不同,Python 3各版本間又有些許不同。有些在Python 3.9能用的語法或功能,不一定可以在Python 3.5使用一樣。
Lua目前已經到了5.4版本,本系列內容也會以Lua 5.4為主。Lua 5.4於2020年釋出,所以還非常的新,有許多實現實際未達到這個標準。但Lua 5.1、5.3也已經使用多年,穩定度是可見的。就算是其餘版本的實現,也具有一定可用性。Lua設計極小,就某種程度上而言,甚至可以相對輕鬆的撰寫自己的實現。類似的於C語言,但是沒有C語言煩人的指標概念。所以Lua的實現也不少,其中著名的有:
極高效即時編譯的Lua實現。相容於Lua 5.1語法。
LuaJIT is compatible to the Lua 5.1 language standard. It doesn't support the implicit arg parameter for old-style vararg functions from Lua 5.0.
用於嵌入式設備開發的Lua,提供許多已經寫好的內容。(沒記錯的話也是使用Lua 5.1標準,沒用過)
Java版的實現,支援Lua 5.2的語法。在GitHub上有另一個Fork的版本,兩個有些差異。
JS實現的版本。其他JS實現的版本還有:Moonshine、lua.vm.js、Starlight。這是一個很新,也有點有趣的項目。我會在談談它。
C#的實現。
【30天Lua重拾筆記01】-認識Lua
認識Luna
盧娜(Luna,又寫作露娜或路娜)是羅馬神話中的月亮女神。「Luna」在法語和義大利語中也有月亮或月神的意思。在希臘神話中她的對應者為塞勒涅。盧娜也常常和黛安娜或赫卡忒混淆在一起。在羅馬的阿文提諾山上建有供奉她的神廟。 — 維基百科
錯棚了…
不是她 😅
再來一次 - 認識Lua
Lua是葡萄牙文的月亮,是一個輕量、快速、容易學習且容易嵌入的程式語言。其目標本就是成為一個很容易嵌入其它語言中使用的語言。其精簡的核心只包含一些最基本的功能,啟動速度非常之快。 儘管如此,透過最基本的功能,甚至可以實現多種編程範式。
可嵌入的程式語言
適合嵌入使用的程式語言 在Raspberry Pi上的編譯紀錄
幾乎所有應用都不可能只使用一個程式語言完成。
忘了是那看過的,現在想想,當時他所說的,或許不只是ABI、與C交互、或是組合語言,像是應用層面的HTML,或是通訊成面的HTTP等,應也可以視為程式語言裡的異語言。既然無可避免,那多少了解一些其他語言也是必須的(不過近期打算更進一步XD),不過我自己初衷很單純是「興趣」(原來是興趣使然的程式語言研究員阿)。
之前曾經找過適合作為嵌入式,嵌入到其他程式語言的腳本語言有哪些。
當然是受到最適合、也是以此為目標設計的Lua影響,但Lua及其簡單,就表達層面上,不適合作為複雜應用。
拿個簡單的例子來說,同為原形設計的物件導向程式語言–EMCAScript後來在ES6也有了class
的語法糖。
總之,Lua很簡單,執行效率也極高,這想點我想是相輔相成的,也沒比要為了語法糖提高其語言解析(parse)的難度。但同樣作為一個圖靈完全(Turing completeness)的程式語言,Lua必然也可以實現複雜應用。
但既然Lua已經幾乎達到其設計目標,那如果需要其他設計方法怎辦,作法之一是使用Python這類膠水語言(glue language,Lua也是),但不管是CPython、CRuby還是Node.js恐怕實現都有些過於「肥大」。繞個路的作法就是使用其他實現,接著帶大伙看看幾個我認為有潛力的語言實現。