반복자와 제네릭 for
반복자와 클로저
먼저 간단한 반복자를 구현해보겠습니다. 반복자는 내부 상태를 유지하는 클로저를 통해 구현됩니다.
-- 팩토리 함수: 테이블을 받아 반복자를 반환
local function factory(tbl)
local index = 0
return function()
index = index + 1
return tbl[index]
end
end
data = {10, 20, 30}
iterator = factory(data) -- 팩토리 호출하여 반복자 생성
while true do
local value = iterator() -- 반복자 호출
if value == nil then
break
end
print(value)
end
그러나 제네릭 for를 사용하면 더 간결하게 표현할 수 있습니다. 제네릭 for는 이러한 반복 작업을 위해 설계되었습니다.
data = {10, 20, 30}
-- 내부에서 반복 함수를 관리하므로 별도의 변수 불필요
for value in factory(data) do
print(value)
end
다음은 파일에서 단어를 읽어 출력하는 예제입니다. 반복자 자체는 복잡하지만 사용은非常简单합니다.
function extractWords()
local lineContent = io.read()
local position = 1
return function()
while lineContent do
local word, endPos = string.match(lineContent, "(%w+)()", position)
if word then
position = endPos
return word
else
lineContent = io.read()
position = 1
end
end
return nil
end
end
for word in extractWords() do
print(word)
end
무상태 반복자
상태를 저장하지 않는 반복자는 여러 반복문에서 재사용할 수 있어 새로운 클로저 생성开销을 절약할 수 있습니다.
local function processFunc(tbl, idx)
idx = idx + 1
print("process idx = " .. idx)
local element = tbl[idx]
if element then
return idx, element
end
end
local function makeIterator(tbl)
return processFunc, tbl, 0
end
local sample = {111, 222, nil, 555}
for key, val in makeIterator(sample) do
print(key .. " " .. val)
end
출력:
process idx = 1
1 111
process idx = 2
2 222
process idx = 3
pairs 함수는 ipairs와 유사하지만, 기본 함수인 next를 반복 함수로 사용합니다.
local function iteratePairs(tbl)
return next, tbl, nil
end
local sample = {111, 222, nil, 555}
for key, val in iteratePairs(sample) do
print(key .. " " .. val)
end
출력:
1 111
2 222
4 555
테이블의 키를 알파벳순으로 정렬하여 출력하는 예제:
local functionNames = {
["luaH_set"] = 10,
["luaH_get"] = 24,
["luaH_present"] = 48,
}
local nameList = {}
for name in pairs(functionNames) do
nameList[#nameList + 1] = name
end
table.sort(nameList)
for _, name in ipairs(nameList) do
print(name)
end
출력:
luaH_get
luaH_present
luaH_set
반복자를 사용하여 키 순서로 정렬된 함수를 출력:
local functionNames = {
["luaH_set"] = 10,
["luaH_get"] = 24,
["luaH_present"] = 48,
}
function sortByKey(tbl, compareFunc)
local keys = {}
for key in pairs(tbl) do
keys[#keys + 1] = key
end
table.sort(keys, compareFunc)
local current = 0
return function()
current = current + 1
return keys[current], tbl[keys[current]]
end
end
for name, num in sortByKey(functionNames) do
print(name, num)
end
출력:
luaH_get 24
luaH_present 48
luaH_set 10
메타테이블과 메타메서드
메타테이블의 메타메서드는 값이 특정 연산에直面했을 때의 동작을 정의합니다.
local Set = {}
local metaTable = {}
function Set.create(list)
local instance = {}
setmetatable(instance, metaTable)
for _, v in ipairs(list) do
instance[v] = true
end
return instance
end
function Set.union(a, b)
if getmetatable(a) ~= metaTable or getmetatable(b) ~= metaTable then
error("집합이 아닌 값을 연산하려고 합니다", 2)
end
local result = Set.create{}
for k in pairs(a) do
result[k] = true
end
for k in pairs(b) do
result[k] = true
end
return result
end
function Set.intersection(a, b)
local result = Set.create{}
for k in pairs(a) do
result[k] = b[k]
end
return result
end
function Set.toString(set)
local elements = {}
for e in pairs(set) do
elements[#elements + 1] = tostring(e)
end
return "{" .. table.concat(elements, ", ") .. "}"
end
-- 관계 연산자 메타메서드 설정
metaTable.__le = function(a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
metaTable.__lt = function(a, b)
return a <= b and not(b <= a)
end
metaTable.__eq = function(a, b)
return a <= b and b <= a
end
-- 산술 연산자 메타메서드 설정
metaTable.__add = Set.union
metaTable.__mul = Set.intersection
metaTable.__tostring = Set.toString
local set1 = Set.create{10, 20, 30, 50}
local set2 = Set.create{30, 1}
print(getmetatable(set1))
print(getmetatable(set2))
print(set1)
print(set1 + set2)
print(set1 * set2)
set1 = Set.create{2, 4}
set2 = Set.create{4, 10, 2}
print(set1 <= set2)
print(set1 < set2)
print(set1 >= set1)
print(set1 > set1)
print(set1 == set2 * set1)
metaTable.__metatable = "protected"
print(getmetatable(set1))
테이블 관련 메타메서드: __index와 __newindex
local prototype = {x = 0, y = 0, width = 100, height = 100}
local mt = {}
function createObject(params)
setmetatable(params, mt)
return params
end
mt.__index = function(_, key)
return prototype[key]
end
mt.__newindex = function(_, key, value)
return
end
obj = createObject{x = 10, y = 20}
print(obj.x)
print(obj.width)
mt.__index = prototype
print(obj.width)
obj.newField = 100
print(obj.newField)
기본값을 가진 테이블:
local uniqueKey = {}
local mt = {__index = function(t) return t[uniqueKey] end}
function setDefaultValue(tbl, default)
tbl[uniqueKey] = default
setmetatable(tbl, mt)
end
table1 = {x = 10, y = 20}
print(table1.z)
setDefaultValue(table1, 0)
print(table1.z)
table2 = {x = 10, y = 20}
setDefaultValue(table2, 1)
print(table2.z)
print(table1.z)
테이블 접근 추적:
function monitor(tbl)
local proxy = {}
local mt = {
__index = function(_, k)
print("*접근: " .. tostring(k))
return tbl[k]
end,
__newindex = function(_, k, v)
print("*수정: " .. tostring(k) .. " → " .. tostring(v))
tbl[k] = v
end,
__pairs = function()
return function(_, k)
local nextKey, nextValue = next(tbl, k)
if nextKey ~= nil then
print("*순회: " .. tostring(nextKey))
end
return nextKey, nextValue
end
end,
__len = function() return #tbl end
}
setmetatable(proxy, mt)
return proxy
end
t = {[2] = "hi"}
print(t[2])
t = monitor(t)
print(t[2])
t[2] = "hello"
print(t[2])
t = monitor({10, 20})
print(#t)
for k, v in pairs(t) do print(k, v) end
읽기 전용 테이블:
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(t, k, v)
error("읽기 전용 테이블을 수정할 수 없습니다", 2)
end
}
setmetatable(proxy, mt)
return proxy
end
days = readOnly{
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
}
print(days[1])
days[2] = "Noday"
객체지향 프로그래밍
프로토타입 기반 언어의 접근법을 참고하여 Lua에서 클래스를 흉내낼 수 있습니다. 다른 객체의 프로토타입이 될 특별한 객체를 만들기만 하면 됩니다. 객체 A의 프로토타입을 B로 설정하려면:
setmetatable(A, {__index = B})
이제 A에서 찾을 수 없는 연산은 B에서 검색됩니다. B를 A의 클래스로 보는 것은 용어상의 변화일 뿐입니다.
클래스 사용 예제:
Account = {
balance = 0,
withdraw = function(self, amount)
if amount > self.balance then
error("잔액 부족")
end
self.balance = self.balance - amount
end
}
function Account.deposit(self, amount)
self.balance = self.balance + amount
end
function Account.new(self, obj)
obj = obj or {}
self.__index = self
setmetatable(obj, self)
return obj
end
a = Account:new{balance = 0}
a:deposit(300.00)
a:withdraw(100.00)
print(a.balance)
-- 상속
SpecialAccount = Account:new()
s = SpecialAccount:new{limit = 1000.00}
s:deposit(600.00)
function SpecialAccount.withdraw(self, amount)
if amount - self.balance >= self:getLimit() then
error("잔액 부족")
end
self.balance = self.balance - amount
end
function SpecialAccount.getLimit(self)
return self.limit or 0
end
s:withdraw(800.00)
print(s.balance)
-- 다중 상속 구현
local function search(key, parentList)
for i = 1, #parentList do
local value = parentList[i][key]
if value then return value end
end
end
function createClass(...)
local c = {}
local parents = {...}
setmetatable(c, {__index = function(t, k)
return search(k, parents)
end})
setmetatable(c, {__index = function(t, k)
local v = search(k, parents)
t[k] = v
return v
end})
c.__index = c
function c:new(obj)
obj = obj or {}
setmetatable(obj, c)
return obj
end
return c
end
Named = {}
function Named:getname()
return self.name
end
function Named:setname(n)
self.name = n
end
NA = createClass(Account, Named)
acc = NA:new{name = "Paul"}
print(acc:getname())
비공개성 구현:
function createAccount(initialBalance)
local state = {balance = initialBalance}
local withdraw = function(amount)
state.balance = state.balance - amount
end
local deposit = function(amount)
state.balance = state.balance + amount
end
local getBalance = function()
return state.balance
end
return {
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance
}
end
acc1 = createAccount(100.00)
acc1.withdraw(40.00)
print(acc1.getBalance())
function createAccount(initialBalance)
local state = {
balance = initialBalance,
LIMIT = 10000.00,
}
local extra = function()
if state.balance > state.LIMIT then
return state.balance * 0.10
else
return 0
end
end
local getBalance = function()
return state.balance + extra()
end
return {
getBalance = getBalance
}
end
acc1 = createAccount(100000.00)
print(acc1.getBalance())
단일 메서드 객체를 사용한 반복자 구현:
function createObject(value)
return function(action, v)
if action == "get" then
return value
elseif action == "set" then
value = v
else
error("잘못된 작업")
end
end
end
d = createObject(0)
print(d("get"))
d("set", 10)
print(d("get"))
이중 표현 방식을 이용한 비공개성:
local balance = {}
Account = {}
function Account.withdraw(self, v)
balance[self] = balance[self] - v
end
function Account.deposit(self, v)
balance[self] = balance[self] + v
end
function Account.balance(self)
return balance[self]
end
function Account.new(self, obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
balance[obj] = 0
return obj
end
a = Account:new{}
a:deposit(100.00)
print(a:balance())