216 lines
3.9 KiB
Markdown
216 lines
3.9 KiB
Markdown
# Lua OOP + Types Tutorial
|
|
|
|
This project provides a small OOP layer with Python-like ergonomics and a set of Python-inspired types.
|
|
|
|
Contents
|
|
- OOP: class definitions as functions, inheritance, visibility, auto-bound methods
|
|
- Types: Int, Float, Str, Bool, Tuple, List (linked), Queue, Stack, Dict, Set
|
|
|
|
---
|
|
|
|
## OOP quick start
|
|
|
|
The OOP system lets you define a class as a function that receives `cls`. Instances are created by calling the class itself.
|
|
|
|
```lua
|
|
oop = require("oop/oop")
|
|
|
|
function Animal(cls)
|
|
function cls.__init(this, name)
|
|
this.name = name
|
|
end
|
|
|
|
function cls.speak(this)
|
|
print(this.name .. " makes a noise.")
|
|
end
|
|
end
|
|
|
|
function Dog(cls)
|
|
cls.inherit(Animal)
|
|
|
|
function cls.speak(this)
|
|
cls.super.speak(this)
|
|
print(this.name .. " barks!")
|
|
end
|
|
end
|
|
|
|
local d = Dog("Milo")
|
|
d.speak()
|
|
```
|
|
|
|
### How instance calls work
|
|
|
|
Methods are defined with dot syntax and explicit `this`:
|
|
|
|
```lua
|
|
function cls.move(this, dx, dy)
|
|
this.x = this.x + dx
|
|
this.y = this.y + dy
|
|
end
|
|
```
|
|
|
|
Calls also use dot syntax. `this` is auto-passed for you:
|
|
|
|
```lua
|
|
obj.move(1, 2)
|
|
```
|
|
|
|
Do not use `:` calls with this system because methods are already auto-bound.
|
|
|
|
---
|
|
|
|
## Visibility (public / protected / private)
|
|
|
|
Visibility is checked when reading or writing members. Use `cls.visibility` or `Visibility`:
|
|
|
|
```lua
|
|
function User(cls)
|
|
cls.setattr(cls, "name", nil, cls.visibility.PUBLIC)
|
|
cls.setattr(cls, "password", nil, cls.visibility.PRIVATE)
|
|
|
|
cls.method("set_password", function(this, pw)
|
|
this.password = pw
|
|
end, cls.visibility.PRIVATE)
|
|
|
|
function cls.__init(this, name, pw)
|
|
this.name = name
|
|
this.password = pw
|
|
end
|
|
end
|
|
```
|
|
|
|
- public: accessible everywhere
|
|
- protected: accessible in the class and subclasses
|
|
- private: accessible only inside the class itself
|
|
|
|
Errors show where the access was attempted and from which class.
|
|
|
|
---
|
|
|
|
## Inheritance
|
|
|
|
Use `cls.inherit(ParentClass)`:
|
|
|
|
```lua
|
|
function Base(cls)
|
|
function cls.hello(this)
|
|
print("base")
|
|
end
|
|
end
|
|
|
|
function Sub(cls)
|
|
cls.inherit(Base)
|
|
function cls.hello(this)
|
|
cls.super.hello(this)
|
|
print("sub")
|
|
end
|
|
end
|
|
```
|
|
|
|
`cls.super` exposes the direct parent only.
|
|
|
|
---
|
|
|
|
## Types module
|
|
|
|
All types live in `oop/types.lua`:
|
|
|
|
```lua
|
|
local Types = require("oop/types")
|
|
```
|
|
|
|
### Primitive types (immutable)
|
|
|
|
Primitives are immutable after construction:
|
|
- `Int`
|
|
- `Float`
|
|
- `Str`
|
|
- `Bool`
|
|
- `Tuple`
|
|
|
|
You can call a primitive to get its value, and use `.copy()` to create a new one.
|
|
|
|
```lua
|
|
local i = Types.Int(42)
|
|
print(i()) -- 42
|
|
print(i.copy()) -- new Int
|
|
```
|
|
|
|
Tuple stores items immutably:
|
|
|
|
```lua
|
|
local t = Types.Tuple({ 1, 2, 3 })
|
|
print(t()) -- immutable items proxy
|
|
print(t.get(2)) -- 2
|
|
```
|
|
|
|
### Mutable types
|
|
|
|
- `List` (linked list)
|
|
- `Queue`
|
|
- `Stack`
|
|
- `Dict`
|
|
- `Set`
|
|
|
|
Examples:
|
|
|
|
```lua
|
|
local l = Types.List({ "a", "b" })
|
|
l.append("c")
|
|
print(l, #l, l.get(2))
|
|
|
|
local q = Types.Queue({ 1, 2 })
|
|
q.enqueue(3)
|
|
print(q.dequeue(), q.peek())
|
|
|
|
local st = Types.Stack({ 9, 8 })
|
|
st.push(7)
|
|
print(st.pop(), st.peek())
|
|
|
|
local d = Types.Dict({ one = 1, two = 2 })
|
|
print(d.get("two"))
|
|
|
|
d.set("three", 3)
|
|
print(#d)
|
|
|
|
local s = Types.Set({ "x", "y" })
|
|
s.add("z")
|
|
print(s.has("x"), #s)
|
|
```
|
|
|
|
---
|
|
|
|
## Dunder methods (operators)
|
|
|
|
Classes support common Lua metamethods. If you do not override them, they raise an error.
|
|
|
|
Example vector:
|
|
|
|
```lua
|
|
function Vec2(cls)
|
|
function cls.__init(this, x, y)
|
|
this.x = x
|
|
this.y = y
|
|
end
|
|
|
|
function cls.__add(a, b)
|
|
return Vec2(a.x + b.x, a.y + b.y)
|
|
end
|
|
|
|
function cls.__tostring(this)
|
|
return "Vec2(" .. this.x .. ", " .. this.y .. ")"
|
|
end
|
|
end
|
|
|
|
local v = Vec2(1, 2) + Vec2(3, 4)
|
|
print(v)
|
|
```
|
|
|
|
---
|
|
|
|
## Notes and tips
|
|
|
|
- Class functions with capitalized names are auto-wrapped into classes when assigned to globals.
|
|
- Methods must be called with dot syntax, not colon syntax.
|
|
- `new(Class, ...)` still works, but `Class(...)` is preferred.
|