Skip to content

Commit 4280401

Browse files
committed
main
1 parent 62bcd27 commit 4280401

File tree

7 files changed

+356
-37
lines changed

7 files changed

+356
-37
lines changed

docs/document/Skill/Lua/docs/Control Flow.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,25 @@ end
1616

1717
_ = cond and fn() or canbe_nil -- canbe_nil might be picked when fn() returns falsy value which is unexpected
1818
```
19+
20+
## Safe Call
21+
22+
- `pcall(fn: function, args: ...any): boolean, ...any`
23+
24+
```lua
25+
if pcall(function() error('foo') end) then
26+
print('not reachable here')
27+
end
28+
```
29+
30+
## Local Scope
31+
32+
Use `do .. end` to create a isolated scope for certain operations.
33+
34+
```lua
35+
do
36+
local foo = 'foo'
37+
end
38+
39+
print(foo) -- Undefined global foo -- [!code warning]
40+
```
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Coroutine
2+
3+
## Overview
4+
5+
Coroutine differs from the abstraction of thread from other languages, coroutine has more collaborative nature while thread are primitively parallel.
6+
That is, coroutine in lua is neither concurrent nor parallel, lua is like single-threaded, handles only one thing at a time and can switch control between coroutines.
7+
Thusly, one should never use words such as *thread* or *multi-threading* when introducing the use of lua coroutine.
8+
9+
> [!NOTE]
10+
> Interestingly, coroutine in `lua` has type name `thread`, even though it is not a real thread.
11+
12+
- `coroutine.create(fn: function): thread`: coroutine factory
13+
- `coroutine.status(co: thread): string`: status of a coroutine
14+
- `running`: yeah it's running
15+
- `normal`:
16+
- `suspended`: created but not started, or suspended on half way
17+
- `dead`: terminated due to error
18+
- `coroutine.yield(...any): ...any`: return control back to caller, suspend the containing coroutine(should call it inside coroutine function body)
19+
- could pass arbitrary return values that could be captured by outer `coroutine.resume`
20+
- can capture arbitrary values as return values from outer `coroutine.resume` **after resume**.
21+
- of course `return` can also return values but terminates coroutine, no worry.
22+
- `coroutine.resume(co: thread, args: ...any): boolean, ...any`: **a blocking operation** to start or resume a `suspended` coroutine
23+
- returns `boolean` indicating whether resume succeeded and `...any` returned from the resumed coroutine by `coroutine.yield`.
24+
- supports passing args to the wrapped function of the coroutine
25+
- `coroutine.wrap(fn: function): function`: wrap resumption as a capsulated function for simplicity.
26+
27+
## Coordination
28+
29+
Coordination is viable by two basic functions, `coroutine.resume` and `coroutine.yield`.
30+
`coroutine.resume` starts or re-enter the coroutine by optional arguments, such arguments can be arguments for the coroutine function body **on first start**, or as new arguments passed halfway that can be captured by `coroutine.yield` on resume.
31+
Conversely, `coroutine.yield` can hang current coroutine and return values that can be captured by outer `coroutine.resume`.
32+
33+
## The Stack
34+
35+
> A coroutine is similar to a thread (in the sense of multithreading): a line of execution, with its own stack, its own local variables, and its own instruction pointer;
36+
> but sharing global variables and mostly anything else with other coroutines.
37+
> — Programming in Lua, Roberto Ierusalimschy
38+
39+
Each coroutine created in lua has its own stack to store the state of variables and so on. The stack would collected when thread is *dead* or no longer referenced by any variable.
40+
41+
## Iterate by Coroutine
42+
43+
Iterator and coroutine are spiritual cousins in lua, one can implement a iterator using coroutine.
44+
45+
- stores state
46+
- yields value on *each call*
47+
48+
```lua
49+
-- an infinite iterator
50+
local co = coroutine.create(function()
51+
local num = 0
52+
while true do
53+
num = num + 1
54+
coroutine.yield(num)
55+
end
56+
end)
57+
-- each time you resume is like iterate to next item
58+
local _, nxt = coroutine.resume(co)
59+
_, nxt = coroutine.resume(co)
60+
_, nxt = coroutine.resume(co)
61+
_, nxt = coroutine.resume(co)
62+
```
63+
64+
An implementation for `ipairs` using coroutine would be like this:
65+
66+
```lua
67+
--- my ipairs at home using coroutine
68+
--- @generic T
69+
---@param arr T[]
70+
---@return fun(): integer, T
71+
local function iter_arr(arr)
72+
local idx = 1
73+
-- the coroutine itself is part of the state
74+
local co_iter = coroutine.create(function()
75+
while idx <= #arr do
76+
coroutine.yield(idx, arr[idx]) -- yield current
77+
idx = idx + 1
78+
end
79+
end)
80+
-- each call on the iterator should resume the coroutine
81+
return function()
82+
local _, i, val = coroutine.resume(co_iter)
83+
return i, val
84+
end
85+
end
86+
87+
for idx, value in iter_arr { 1, 2, 3, 4, 5 } do
88+
print(idx, value)
89+
end
90+
```
91+
92+
## Wrap Coroutine as Function
93+
94+
Coroutine can be like a consumer which would probably be called for one or more times, it could be trivial to write with plain `_, _ = coroutine.resume(co)` especially when the resumption has return value.
95+
96+
```lua
97+
local co = coroutine.create(function()
98+
while true do
99+
coroutine.yield(math.random(1, 100))
100+
end
101+
end)
102+
103+
local _, nxt = coroutine.resume(co)
104+
_, nxt = coroutine.resume(co)
105+
_, nxt = coroutine.resume(co)
106+
```
107+
108+
With `coroutine.wrap()`, you can enclose it as a function, the function call does not return resumption status as `coroutine.resume` does.
109+
110+
```lua
111+
local consume = coroutine.wrap(function()
112+
while true do
113+
coroutine.yield(math.random(1, 100))
114+
end
115+
end)
116+
117+
local nxt = consume()
118+
nxt = consume()
119+
nxt = consume()
120+
nxt = consume()
121+
```
122+
123+
> [!IMPORTANT]
124+
> The price of `coroutine.wrap` is, you can't track the status of the coroutine since it was wrapped inside.
125+
126+
> [!NOTE]
127+
> The implementation of `coroutine.wrap` can be like this:
128+
>```lua
129+
>--- my coroutine.wrap at home
130+
>---@param fn fun(...any): ...unknown
131+
>---@return fun(...any): ...unknown
132+
>local function wrap(fn)
133+
> local co = coroutine.create(fn)
134+
> return function(...)
135+
> local result = { coroutine.resume(co, ...) }
136+
> if result[1] then return select(2, unpack(result)) end
137+
> end
138+
>end
139+
>```

docs/document/Skill/Lua/docs/Function.md

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ print((function() end)() == nil) -- true
1313
Variadic parameter in lua does not have name and default type, it's a special identifier `...` that has to be expanded to a table using `{ ... }`
1414

1515
Such identifier can be used in:
16-
- `{ ... }`: direct expansion
17-
- `{ a, ..., b }`: append extra items around
18-
- `select('#', ...)` and `select(idx, ...)` to get length or pick one item of the args without intermediate expansion
16+
- `{ ... }`: direct expansion to a table
17+
- `{ a, ..., b }`: append extra items around inside a table
18+
- `select('#', ...)` and `select(idx, ...)` to get length or pick one item from args without intermediate expansion
1919

2020
```lua
2121
local function foo(...)
@@ -37,23 +37,45 @@ end
3737

3838
## Multi-Returns
3939

40-
```lua
41-
local function foo()
42-
return 1, 2, 3
43-
end
40+
- `unpack(list: table, start?: integer, end?: integer)`: splatter items in successive indices from a table
41+
- `select(start: integer, ...any): ...unknown`: slice a splat from start into another splat
42+
- `select(symbol: '#', ...any): ...unknown`: retrieve a length of the splat
43+
- `{ <expr> }`: collect splats from `<expr>`, a function call for example.
44+
```lua
45+
local function foo(...) return unpack { ... } end
46+
_ = { foo(1, 2, 3) } -- [1, 2, 3]
47+
```
4448

45-
local a, b, c = foo()
46-
```
49+
## Iterator Function
4750

48-
## Meta Functions
51+
Iterator in lua can be implemented by closure, which scopes local fields in its containing factory function, and operates iteration within the created iterator function.
4952

50-
Mate Functions are global functions registered by lua runtime but commonly used
53+
- store state in outer scope
54+
- return nil to cease the iteration
55+
- consume by `for .. in` statement(the first return being `nil` would stop the loop)
5156

52-
- `_G.select`: an operator can perform item picking from **arbitrary number of arguments** including `...` or get length of args
53-
```lua
54-
-- get length of args
55-
_ = select('#', 1, 2, 3) -- 3
56-
-- pick second item
57-
_ = select(2, 1, 2, 3) -- 2
58-
```
59-
- `_G.unpack`: equivalent to `table.unpack`
57+
```lua
58+
--- my ipairs impl at home
59+
---@generic T
60+
---@param array T[]
61+
---@return fun(): integer, T
62+
local function iter_array(array)
63+
-- store state in outer scope
64+
local idx = 0 -- [!code highlight]
65+
local current = nil -- [!code highlight]
66+
67+
return function()
68+
idx = idx + 1
69+
if idx <= #array then
70+
current = rawget(array, idx)
71+
return idx, current
72+
end
73+
74+
return nil, nil -- return nil to cease the iteration -- [!code highlight]
75+
end
76+
end
77+
78+
for idx, item in iter_array { 1, 2, 3 } do
79+
print(idx, item)
80+
end
81+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Metatable
2+
3+
Metatable are special field could be injected into a table, which could include custom operators and any arbitrary field for special use(implementing OOP for example)
4+
5+
## Metatable Method
6+
7+
A table literal does not contain metatable on creation.
8+
9+
- `getmetatable(t: table): table`: retrieve metatable of a table
10+
- `setmetatable(t: table, metatable: table): table`: set the metatable to `t` and returns the altered table reference
11+
12+
## Indexing
13+
14+
- `__index`
15+
- `table`: redirects indexing to another `table` **when key is absent from the table**.
16+
- `fun(this: table, key: any): any`: a custom getter to be called **when `rawget(this, idx)` cannot find one value for the key**.
17+
18+
- `__newindex`
19+
- `fun(this: table, key: any, value: any)`: custom setter to set the value to key **only when the key is absent from the table**.
20+
21+
### Indexing Proxy
22+
23+
Either `__index` or `__newindex` could only be triggered when key is absent in the table, so if you do need to monitor all access to the table, such as **monitor value updates**, an empty table is required to be the proxy.
24+
The proxy must be empty during the whole lifetime, so that each access to it would trigger `__index` and `__newindex`, and the real table represents the data would be wrapped as a closed variable inside `__newindex` and `__index`.
25+
26+
> [!IMPORTANT]
27+
> However, the price of proxy table is, you can't iterate it by `ipairs` or `pairs`, they don't redirect to `__index`.
28+
29+
```lua
30+
local real = {
31+
name = 'foo',
32+
}
33+
34+
local proxy = setmetatable({}, {
35+
__index = function(_, key) return real[key] end,
36+
__newindex = function(_, key, value)
37+
if real[key] ~= nil then
38+
print('updating the value')
39+
real[key] = value
40+
else
41+
print('setting this value for the first time')
42+
real[key] = value
43+
end
44+
end,
45+
})
46+
47+
proxy.name = 'bar' -- updating the value
48+
```
49+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Object-Oriented Programming
2+
3+
## Inheritance
4+
5+
`__index` made inheritance possible in lua because it can redirects the field accessing to another table, which could act as a prototype.
6+
7+
```lua
8+
local Window = { width = 0 } -- Window is the prototype
9+
10+
function Window:new(obj)
11+
-- self would be the metatable of the new table
12+
-- so here we set the __index to self
13+
-- which would redirect field accessing from the new table to the prototype, self
14+
self.__index = self
15+
-- set the prototype as metatable
16+
-- so that we could access original implementation
17+
-- in conclusion, the self here is more like super in some other languages
18+
return setmetatable(obj or {}, self)
19+
end
20+
21+
local window = Window:new { height = 100 } -- a new object
22+
23+
-- inheritance
24+
-- FloatingWindow is now a child prototype inherited from Window
25+
-- now you can have special implementation on this new class
26+
local FloatingWindow = Window:new {
27+
floating = true,
28+
special_method = function() end,
29+
new = function(self, obj) end, -- you can override constructor too
30+
}
31+
-- because `new` was always targeted to the one implemented on Window
32+
-- and the self now targets to FloatingWindow
33+
local float = FloatingWindow:new()
34+
```
35+
36+
> [!TIP]
37+
> Annotation from `lua_ls` can offer generic type inference, this is highly recommended:
38+
> ```lua
39+
>---@generic T
40+
>---@param self T
41+
>---@param obj? T | table
42+
>---@return T
43+
>function Item:new(obj)
44+
> self.__index = self
45+
> return setmetatable(obj or {}, self)
46+
>end
47+
>```
48+
49+
> [!TIP]
50+
> Regardless of the inheritance, if you want one object partially behave the same as the another(with `__index` pointing to itself) , simply set the another as metatable to the one.
51+
> But you should definitely not set theme as mutual metatables, they would cycle forever.
52+
53+
## Runtime Type Assertion
54+
55+
Similarly you can check whether an instance is of type of a class by comparing its metatable with the class table.
56+
57+
```lua
58+
local Window = { width = 0 }
59+
function Window:new(obj)
60+
self.__index = self
61+
return setmetatable(obj or {}, self)
62+
end
63+
local window = Window:new { height = 100 }
64+
local FloatingWindow = Window:new {}
65+
local float = FloatingWindow:new()
66+
67+
-- type checking -- [!code highlight]
68+
_ = getmetatable(float) == FloatingWindow -- true -- [!code highlight]
69+
-- but this wouldn't work on the 'class' -- [!code highlight]
70+
_ = getmetatable(FloatingWindow) == FloatingWindow -- false -- [!code highlight]
71+
-- but you can tell whether a class 'directly' inherited from another -- [!code highlight]
72+
_ = getmetatable(FloatingWindow) == Window -- true -- [!code highlight]
73+
```
74+
75+
> [!NOTE]
76+
> A recursive comparison along the metatable chain is surely viable. I should make some time to have a example here.
77+
78+
## Multiple Inheritance

0 commit comments

Comments
 (0)