Lua

lua

nil

print(a)
  • null이 아니라 nil일세
  • nil로 초기화되어 있다

comments 토글

--[[
print(10)         -- no action (comment)
--]]

---[[
print(10)         --> 10
--]]
  • 간단하게 블럭 comment를 토글하는 게 인상적
  • 이런 요구가 많았나보다

number는 다 double

The number type represents real (double-precision floating-point) numbers. Lua has no integer type, as it does not need it.

  • 정수 타입따윈 없다.
  • 숫자는 모두 double

table

  • associative array
  • index로 숫자 뿐만 아니라 string도 된다. nil을 제외하곤 다 가능

syntactic sugar

a.name
a["name"]
  • 둘다 동일

expressions

1 ~= 2
  • != 가 아니다
and -- &&
or -- ||
not -- !
  • and, or, not 사용
print("Hello " .. "World")  --> Hello World
print(0 .. 1)               --> 01
  • 숫자를 concatenation 하면 알아서 string으로 바꿔준다.
  • 이거 간편해서 맘에 든다

table constructor

a = {x=0, y=0}
a = {}; a.x=0; a.y=0
  • 동일
polyline = {color="blue", thickness=2, npoints=4,
                 {x=0,   y=0}, -- [1]
                 {x=-10, y=0}, -- [2]
                 {x=-10, y=1}, -- [3]
                 {x=0,   y=1}  -- [4]
           }
print(polyline[2].x)    --> -10
print(polyline[3].y)    --> 1
  • index를 따로 명시하지 않으면 숫자 인덱스가 자동으로 생성
  • 시작이 0이 아니라 1이다

assignment

a, b = 10, 2*x
  • multiple assignment가 가능
x, y = y, x                -- swap `x' for `y'
a[i], a[j] = a[j], a[i]    -- swap `a[i]' for `a[j]'
  • 실행하기 전 evaluate를 전부 다 한다.
    • 값 저장
    • 그래서 이렇게 swap이 가능
a, b, c = 0, 1
print(a,b,c)           --> 0   1   nil
  • 앞에서부터 차례로 할당
  • 그래서 c가 nil

local variables and blocks

j = 10         -- global variable
local i = 1    -- local variable
  • local scope는 상식적으로 동작
local foo = foo
  • idiom
  • global foo 변경이 도중에 있어도 local foo로 할당 당시 값을 보존하고 싶을 때,
  • 속도도 빨라진다
do
    local a2 = 2*a
    local d = sqrt(b^2 - 4*a*c)
    x1 = (-b + d)/a2
    x2 = (-b - d)/a2
end          -- scope of `a2' and `d' ends here
print(x1, x2)
  • do-end를 사용해서 scope를 명시적으로 끝낼 수 있다

control structures

if op == "+" then
  r = a + b
elseif op == "-" then
  r = a - b
elseif op == "*" then
  r = a*b
elseif op == "/" then
  r = a/b
else
  error("invalid operation")
end
local i = 1
while a[i] do
  print(a[i])
  i = i + 1
end
-- print the first non-empty line
repeat
  line = os.read()
until line ~= ""
print(line)
for i=1,f(x) do print(i) end
for i=10,1,-1 do print(i) end -- -1은 step. 생략하면 1이 디폴트
-- print all values of array `a'
for i,v in ipairs(a) do print(v) end

-- print all keys of table `t'
for k in pairs(t) do print(k) end
local i = 1
while a[i] do
  if a[i] == v then break end
  i = i + 1
end
  • continue는 없다

function

multiple results

function foo2 () return 'a','b' end   -- returns 2 results
x,y = foo2()        -- x='a', y='b'
x = foo2()          -- x='a', 'b' is discarded
x,y,z = 10,foo2()   -- x=10, y='a', z='b'
a = {foo2()}         -- a = {'a', 'b'}
  • 바로 테이블로 넣을 수 있다
print(unpack{10,20,30})    --> 10   20   30
a,b = unpack{10,20,30}     -- a=10, b=20, 30 is discarded
print(unpack{a = 10, b= 20, c = 30}) --> 아무것도 안 나와. 숫자 인덱스만 가능
  • unpack 으로 테이블 값을 multiple results로 변환할 수 있다.
print(string.find("hello hello", " hel")) --> 6	9
  • multiple results가 지원되니 이런 것도 되는구나
  • 직관적이다. ” hel”을 찾았고 index 6에서부터 9까지이다

variable number of arguments

printResult = ""

function printConcat (...)
  for i,v in ipairs(arg) do
        printResult = printResult .. tostring(v) .. "\t"
  end
  printResult = printResult .. "\n"
end

printConcat(2, 5, 1, "aaww", "test")
print(printResult)
  • … 으로 받을 수 있다.
  • arg 테이블로 넘어옴
local _, x = string.find("hello hello", " hel") --> _:6  x:9
print(x) --> 9
  • _ 로 익명 변수로 받을 수 있다. 따로 dummy와 같이 이름을 안 지어도 되니 편하다
function selecting (n, ...)
        return arg[n]
end

print(selecting(1, string.find("hello hello", " hel"))) --> 6
  • select 라는 내부 함수가 있다
  • 하지만 select를 사용하니 6만 나와야 하는데, 6 9가 나옴
function fwrite (fmt, ...)
  return io.write(string.format(fmt, unpack(arg)))
end
  • 이렇게 unpack로 multiple results로 변환해 넘길 수 있다

named arguments

function rename (arg)
  print("old - "..arg.old..", new - "..arg.new)
end

rename{old="temp.lua", new="temp1.lua"}
rename{old="temp.lua", new="temp1.lua"}
rename({old="temp.lua", new="temp1.lua"})
  • 두개 같은 뜻

closures

lexical scoping

When a function is written enclosed in another function, it has full access to local variables from the enclosing function

external local variable

function sortbygrade (names, grades)
    table.sort(names, function (n1, n2)
        return grades[n1] > grades[n2]    -- compare the grades
    end)
end
  • anonymous function이 grades에 접근한다. 직접 인자로 넘겨주지도 않았는데.
  • global variable, local variable도 아닌 external local variable이라고 부른다.
    • 혹은 upvalue

closure

function newCounter ()
        local i = 0
        return function ()   -- anonymous function
                i = i + 1
                return i
        end
end

c1 = newCounter()
print(c1())  --> 1
print(c1())  --> 2
c2 = newCounter()
print(c2())  --> 1
print(c1())  --> 3
print(c2())  --> 2
  • function과 function이 접근하는 모든 upvalue를 넣어준다.

callback에 유용하게 사용

function digitButton (digit)
        return Button
        {
                label = digit,
                action = function ()
                        add_to_display(digit)
                end
        }
end

function을 손쉽게 교체하기

do
        local oldSin = math.sin
        local k = math.pi/180
        math.sin = function (x)
                return oldSin(x*k)
        end
end
  • math.sin을 이렇게 손쉽게 바꿀 수 있다

sandbox

do
        local oldOpen = io.open
        io.open = function (filename, mode)
                if access_OK(filename, mode) then
                        return oldOpen(filename, mode)
                else
                        return nil, "access denied"
                end
        end
en
  • 이렇게 손쉽게 sandbox를 만들 수 있다

non-global functions

table에 function을 할당

Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end

-- or

Lib = {
        foo = function (x,y) return x + y end,
        goo = function (x,y) return x - y end
}

-- or

Lib = {}
function Lib.foo (x,y)
        return x + y
end
function Lib.goo (x,y)
        return x - y
end
  • first-class functions 성질 때문에 가능

syntactic sugar

local f = function (...)
        ...
end

-- or

local function f (...)
        ...
end

proper tail calls

function foo (n)
        if n > 0 then return foo(n - 1) end
end
  • stack 소모없이 호출 가능.
  • 물론 return 뒤에 다른 문이 없어야 한다.

anonymous function

function list_iter (t)
        local i = 0
        local n = table.getn(t)
        return function ()
                i = i + 1
                if i <= n then return t[i] end
        end
end
  • function에 이름은 안 붙일 수 있다
  • c++ 함수 객체처럼 사용할 수 있음

iterators and the generic for

iterators and closures

function list_iter (t)
        local i = 0
        local n = table.getn(t)
        return function ()
                i = i + 1
                if i <= n then return t[i] end
        end
end

t = {10, 20, 30}
iter = list_iter(t)    -- creates the iterator
while true do
        local element = iter()   -- calls the iterator
        if element == nil then break end
        print(element)
end
  • iterator를 따로 로컬 변수에 할당해서 진행시켜도 되지만
t = {10, 20, 30}
for element in list_iter(t) do
        print(element)
end
  • in clause 에 iterator factory를 넣어서 편하게 사용할 수 있다
  • 내부에서 iterator를 저장해서 진행시켜 줌
  • 순회 관련은 iterator factory를 적극 사용

stateless iterators

function iter (a, i)
        i = i + 1
        local v = a[i]
        if v then
                return i, v
        end
end

function ipairs (a)
        return iter, a, 0
end
  • ipairs가 대표적인 예
    • iter : iterator
    • a : invariant state
    • 0 : control value 초기값
  • iterator state로 복잡한 것도 넘길 수 있다
  • nil을 리턴할 때까지
    • iter(a, 0), iter(a, 1), … lua가 호출
  • 구현에서 보다싶이 **index를 사용하는 경우에만 ipairs**를 사용할 수 있다.
a = {x = "one", y = "two", z = "three"}

function pairs (t)
        return next, t, nil
end

for k, v in pairs(a) do
        print(k, v)
end

for k, v in next, a do
        print(k, v)
end
  • pairs 내부에서 lua primitive인 next를 호출
  • 순서 보장 안 함
  • index는 물론 다른 key도 순회할 수 있다
  • next, a 로 pairs를 안 쓰고 직접 사용할수도 있다

require

dofile과 비슷하나

  • path에서 파일을 찾는다
  • 똑같은 파일을 중복해서 실행하지 않는다
  • 이것 때문에 dofile보다 require가 선호

패턴 사용가능

?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
  • require “lili”를 했다면 ?가 lili로 치환된다
  • LUA_PATH 환경 변수에 정의되어 있다
lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua

coroutines

  • kind of (non-preemptive) multithreading

yield, resume

co = coroutine.create(function ()
        for i=1,3 do
                print("co", i)
                coroutine.yield()
        end
end)

coroutine.resume(co) --> co 1
print(coroutine.status(co)) --> suspended
coroutine.resume(co) --> co 2
coroutine.resume(co) --> co 3
coroutine.resume(co) --> 아무일도 하지 않는다
print(coroutine.status(co)) --> dead
print(coroutine.resume(co)) --> false	cannot resume dead coroutine
  • 생성시 suspeded 상태
  • yield 호출로 suspeded 상태로 만들고 제어권을 넘긴다

yield, resume간 데이터 교환

resume -> coroutine main func

co = coroutine.create(function (a,b,c)
        print("co", a,b,c)
end)

coroutine.resume(co, 1, 2, 3)    --> co  1  2  3

resume -> yield

co = coroutine.create (function ()
        print("co", coroutine.yield())
end)

coroutine.resume(co)
coroutine.resume(co, 4, 5)     --> co  4  5
  • resume에서 넘긴 아규먼트를 yield가 리턴

coroutine main func -> resume

co = coroutine.create(function ()
        return 6, 7
end)
print(coroutine.resume(co))   --> true  6  7
  • coroutine이 종료됐을 때

yield -> resume

co = coroutine.create(function (a,b)
        coroutine.yield(a + b, a - b)
end)

print(coroutine.resume(co, 20, 10))  --> true  30  10
  • resume 첫번째 리턴값은 실행 성공여부(true/false) 그 다음에 yield로 넘긴 값이 온다

asymmetric coroutines

  • 루아에서 제공하는 코루틴 분류
  • 다른 함수 간에 yield, resume이 가능
  • 다른 언어에서 제공하는 symmetric coroutines
    • 같은 함수에서만 가능

예제 producer-consumer

function receive (prod)
        local status, value = coroutine.resume(prod)
        return value
end

function send (x)
        coroutine.yield(x)
end

function producer ()
        return coroutine.create(function ()
                while true do
                        local x = io.read()     -- produce new value
                        send(x)
                end
        end)
end

function filter (prod)
        return coroutine.create(function ()
                local line = 1
                while true do
                        local x = receive(prod)   -- get new value
                        x = string.format("%5d %s", line, x)
                        send(x)      -- send it to consumer
                        line = line + 1
                end
        end)
end

function consumer (prod)
        while true do
                local x = receive(prod)   -- get new value
                io.write(x, "\n")          -- consume new value
        end
end

consumer(filter(producer()))

coroutines as iterators

function permgen (a, n)
        if n == 0 then
                coroutine.yield(a)
        else
                for i=1,n do

                -- put i-th element as the last one
                a[n], a[i] = a[i], a[n]

                -- generate all permutations of the other elements
                permgen(a, n - 1)

                -- restore i-th element
                a[n], a[i] = a[i], a[n]

                end
        end
end

function perm (a)
        local n = table.getn(a)
        return coroutine.wrap(function () permgen(a, n) end)
end

function printResult (a)
        for i,v in ipairs(a) do
                io.write(v, " ")
        end
                io.write("\n")
end

for p in perm{"a", "b", "c"} do
        printResult(p)
end
  • 재귀함수를 깔끔하게 iterator factory로 구현할 수 있다
  • 다 coroutines 덕분

data structures

  • lua에서는 table로 다 구현

arrays

  • 정수 indexing tables
a = {}    -- new array
for i=1, 1000 do
        a[i] = 0
end
  • index는 정수면 된다.
    • -5로 시작하든 0으로 시작하든 상관 X
  • 하지만 1로 시작이 lua lib convention

matrices and multi-dimensional arrays

local N = 10
local M = 10

mt = {}          -- create the matrix
for i=1,N do
        mt[i] = {}     -- create a new row
        for j=1,M do
                mt[i][j] = 0
        end
end

linked lists

list = nil -- head

list = {next = list, value = "val"}

local l = list
while l do
        print(l.value)
        l = l.next
end

queues and double queues

List = {}

function List.new ()
        return {first = 0, last = -1}
end

function List.pushleft (list, value)
        local first = list.first - 1
        list.first = first
        list[first] = value
end

function List.pushright (list, value)
        local last = list.last + 1
        list.last = last
        list[last] = value
end

function List.popleft (list)
        local first = list.first
        if first > list.last then error("list is empty") end

        local value = list[first]
        list[first] = nil        -- to allow garbage collection
        list.first = first + 1
        return value
end

function List.popright (list)
        local last = list.last
        if list.first > last then error("list is empty") end

        local value = list[last]
        list[last] = nil         -- to allow garbage collection
        list.last = last - 1
        return value
end

sets and bags

reserved =
{
        ["while"] = true,
        ["end"] = true,
        ["function"] = true,
        ["local"] = true,
}

for w in allwords() do
        if reserved[w] then
                -- `w' is a reserved word
                ...

string buffers

local t = {}
for line in io.lines() do
        table.insert(t, line)
end

s = table.concat(t, "\n") .. "\n"
  • table, table.concat 사용
-- WARNING: bad code ahead!!
local buff = ""
for line in io.lines() do
        buff = buff .. line .. "\n"
end
  • garbage-collection을 하긴 하나 중간에 메모리가 많이 할당돼서 성능에 불이익
  • 나쁜 코드

metatables and metamethods

table은 못하는 테이블 추가, 비교, 호출을 할 수 있게 해준다

arithmetic metamethods

Set = {}
Set.mt = {}    -- metatable for sets

function Set.new (t)
        local set = {}
        setmetatable(set, Set.mt)
        for _, l in ipairs(t) do
                set[l] = true
        end

        return set
end

function Set.union (a,b)
        local res = Set.new{}

        for k in pairs(a) do
                res[k] = true
        end

        for k in pairs(b) do
                res[k] = true
        end

        return res
end

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
assert(getmetatable(s1) == getmetatable(s2))

Set.mt.__add = Set.union -- metamethod를 추가

s3 = s1 + s2  --> {1, 30, 10, 50, 20}

relational metamethods

  • __eq (equality)
  • __lt (less than)
  • __le (less or equal)
Set.mt.__le = function (a,b)
        for k in pairs(a) do
                if not b[k] then return false end
        end
        return true
end

Set.mt.__lt = function (a,b)
        return a <= b and not (b <= a)
end

library-defined metamethods

Set.mt.__tostring = Set.tostring

s1 = Set.new{10, 4, 5}
print(s1)    --> {4, 5, 10}
  • print는 tostring을 사용
  • tostring은 메타테이블 __tostring 필드(메타필드)를 제일 먼저 체크

table-access metamethods

the __index metamethod

  • 테이블에 없는 field 접근 시 __index metamethod 호출
    • 일반적으로 여기서 nil을 리턴
  • 사용하면 기본값으로부터 쉽게 값을 상속받을 수 있다
-- create a namespace
Window = {}

-- create the prototype with default values
Window.prototype = {x=0, y=0, width=100, height=100, }

-- create a metatable
Window.mt = {}

-- declare the constructor function
function Window.new (o)
        setmetatable(o, Window.mt)
        return o
end

--------------------------------------------------
Window.mt.__index = function (table, key)
      return Window.prototype[key]
end

-- 이 예제에선 동작이 필요한 게 아니라
-- 그냥 테이블 할당이므로 이렇게 간단하게 적을 수 있다
-- Window.mt.__index = Window.prototype
--------------------------------------------------

w = Window.new{x=10, y=20}
print(w.width) --> 100

the __newindex metamethod

  • __index는 없는 필드를 읽으려고 할 때 호출하는 메타메서드
  • __newindex는 없는 필드를 쓰려고 할 때 호출하는 메타머서드
    • default로 rawset(t, k, v) 호출
  • 이걸 사용하면 read-only table을 만들 수 있다

tables with default values

function setDefault (t, d)
        local mt =
        {
                __index = function ()
                        return d
                end
        }
        setmetatable(t, mt)
end

tab = {x=10, y=20}
print(tab.x, tab.z)     --> 10   nil

setDefault(tab, 0)
print(tab.x, tab.z)     --> 10   0
exclusive field 필드 사용
  • 메타테이블을 공유하는데, 테이블마다 리턴값을 다르게 하고 싶다
  • 메타테이블 값을 변경하면 공유하는 테이블 모두에게 영향
  • ___ 를 사용하라. unique한 이름 생성을 lua에서 책임지기 때문에 이름 걱정은 하지 말 것
    • c++ anonymous namespace 처럼
local mt =
{
        __index = function(t)
                return t.___
        end
}

function set_default(t, d)
        t.___ = d
        setmetatable(t, mt)
end

tab_a = {}
tab_b = {}

set_default(tab_a, 0)
print(tab_a.no) --> 0
print(tab_b.no) --> nil

set_default(tab_b, 1)
print(tab_a.no) --> 0
print(tab_b.no) --> 1
local key = {}    -- unique key
local mt = {__index = function (t) return t[key] end}
function setDefault (t, d)
        t[key] = d
        setmetatable(t, mt)
end
  • 아님 이렇게 table을 key로 사용할수도 있다

tracking table accesses

t = {} -- original table (created somewhere)

-- keep a private access to original table
local _t = t

-- create proxy
t = {}

-- create metatable
local mt =
{
        __index = function (t,k)
                print("*access to element " .. tostring(k))
                return _t[k]   -- access the original table
        end,

        __newindex = function (t,k,v)
                print("*update of element " .. tostring(k) .. " to " .. tostring(v))
                _t[k] = v   -- update original table
        end
}

setmetatable(t, mt)

t[2] = 'hello' --> *update of element 2 to hello
print(t[2]) --> *access to element 2
  • proxy를 만들고 __index, __newindex 메타메서드를 사용하면 테이블 접근을 다 추적할 수 있다
하나 만들고 돌려 쓰려면
-- create private index
local index = {}

-- create metatable
local mt =
{
        __index = function (t,k)
                print("*access to element " .. tostring(k))
                return t[index][k]   -- access the original table
        end,

        __newindex = function (t,k,v)
                print("*update of element " .. tostring(k) .. " to " .. tostring(v))
                t[index][k] = v   -- update original table
        end
}

function track (t)
        local proxy = {}
        proxy[index] = t
        setmetatable(proxy, mt)

        return proxy
end


tab_1 = {}
tab_1 = track(tab_1)
tab_1[1] = 'hello'
print(tab_1[1])

tab_2 = {}
tab_2 = track(tab_2)
tab_2[1] = 'hello 2'
print(tab_2[1])

read-only tables

function readOnly (t)
        local proxy = {}
        local mt =
        {       -- create metatable
                __index = t,
                __newindex = function (t,k,v)
                        error("attempt to update a read-only table", 2)
        end
        }

        setmetatable(proxy, mt)
        return proxy
end

days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

print(days[1])    --> Sunday
days[2] = "Noday" --> attempt to update a read-only table

packages

  • lua에서는 패키지를 위한 명시적인 매커니즘은 없다.

table로 패키지를 표현할 수 있다

  • basic lib가 그렇게 하고 있다네.
  • package를 다른 table과 똑같이 취급할 수 있어서 장점이 많다
  • 대부분의 언어에서 package는 first-class value가 아님
    • 변수에 저장할 수 없다
    • 함수 인자로 넘길 수 없다
    • 언어는 package를 지원하는 매커니즘을 지원해야 하고 사용자도 불편

the basic approach

complex = {}

function complex.new (r, i) return {r=r, i=i} end

-- defines a constant `i'
complex.i = complex.new(0, 1)

function complex.add (c1, c2)
        return complex.new(c1.r + c2.r, c1.i + c2.i)
end

function complex.sub (c1, c2)
        return complex.new(c1.r - c2.r, c1.i - c2.i)
end

function complex.mul (c1, c2)
        return complex.new(c1.r*c2.r - c1.i*c2.i, c1.r*c2.i + c1.i*c2.r)
end

function complex.inv (c)
        local n = c.r^2 + c.i^2
        return complex.new(c.r/n, -c.i/n)
end

-- complex는 global variable. 그래서 return이 필요없어 보인다
-- 하지만 패키지를 열었을 때, 패키지를 리턴하는 게 좋다
-- 패키지를 사용하는 다른 방법을 제공하기 때문
-- good practice
return complex
local P = {}
complex = P           -- package name

P.i = {r=0, i=1}
function P.new (r, i) return {r=r, i=i} end

function P.add (c1, c2)
return P.new(c1.r + c2.r, c1.i + c2.i)
end

-- ...
  • 정의하는데 매번 complex를 다 붙여주기 귀찮으니깐 이렇게 정의

privacy

  • local scope를 사용

packages and files

  • 보통 파일 이름과 package 이름을 일치 시킨다
    • 추가로 알아야할 게 적어지므로 좋은 습관
  • 하지만 파일 이름이나 package 이름이 변경되면 다 변경해줘야 해서 번거롭다
local P = {}   -- package

if _REQUIREDNAME == nil then
        complex = P
else
        _G[_REQUIREDNAME] = P
end
  • require 다음에는 파일 이름이 들어간다
    • 그걸 _REQUIREDNAME 로 받을 수 있음
  • 이런 식으로 파일 이름을 package 이름으로 사용
  • _G는 global table

using the global table

  • 노출할 필요가 없는 걸 다 local 붙여줘야 하는데, 이게 실수하기 쉽단 말이지

exclusive environment

  • 패키지 안 모든 함수가 table을 공유하고 global variable을 이 table로 연결
local P = {}
complex = P
-- function environment를 변경. 1은 스택 레벨.
-- 즉 1은 현재 함수, 2는 현재 함수를 호출한 함수
setfenv(1, P)

function add (c1, c2)
        return new(c1.r + c2.r, c1.i + c2.i)
end
  • 함수를 정의할 때, 일일이 complex. 를 안 붙였다.
  • 외부에선 complex.add로 접근
local P = {}
pack = P

-- Import Section:
-- declare everything this package needs from outside
local sqrt = math.sqrt
local io = io

-- no more external access after this point
setfenv(1, P)
  • environment를 변경하면 다른 package에 접근을 못하는데, 이런식으로 설정하기 전에 local로 선언해서 사용

object-oriented programming

  • table도 object. 즉 각자 identity(selfness)를 가진다

colon(:) operator

Account = { balance = 0 }

-- colon operator를 사용하면 첫번째 인자로 self를
-- lua에서 넘겨준다
function Account:withdraw (v)
        self.balance = self.balance - v
end

a = Account
a:withdraw(100.00) -- lua에서 a를 self 패러매터 인자로 넘김
a.withdraw(a, 100.00) -- 명시적으로 a를 넘김

classes

function Account:new (o)
        o = o or {}   -- create object if user does not provide one
        setmetatable(o, self)
        self.__index = self
        return o
end

a = Account:new{balance = 0}
a:deposit(100.00) -- getmetatable(a).__index.deposit(a, 100.00)
  • metatable을 사용해 class를 흉내낼 수 있다

inheritance

SpecialAccount = Account:new()

function SpecialAccount:withdraw (v)
        if v - self.balance >= self:getLimit() then
                error"insufficient funds"
        end
        self.balance = self.balance - v
end

function SpecialAccount:getLimit ()
        return self.limit or 0
end
  • __index metamethod는 없는 필드에 접근할 때, 호출
  • 그래서 위와 같이 override를 쉽게 할 수 있다
    • 이제 SpecialAccount 테이블에 withdraw 필드가 존재하므로 Account:withdraw() 호출 X

privacy

function newAccount (initialBalance)
        local self = {balance = initialBalance} -- 외부 노출 X

        local withdraw = function (v)
                self.balance = self.balance - v
        end

        local deposit = function (v)
                self.balance = self.balance + v
        end

        local getBalance = function ()
                return self.balance
        end

        return
        {
                withdraw = withdraw,
                deposit = deposit,
                getBalance = getBalance
        }
end
  • local table을 만들어서 감출 수 있다.

the single-method approach

function newObject (value)
        return function (action, v)
                if action == "get" then return value
                elseif action == "set" then value = v
                else error("invalid action") end
        end
end

d = newObject(0)
print(d("get"))    --> 0
d("set", 10)
print(d("get"))    --> 10
  • 함수 객체 생각하면 된다

libraries

programming in lua 패스 챕터

  • 10 – Complete Examples
  • 12 – Data Files and Persistence
  • 14 – The Environment
  • 17 – Weak Tables
  • 18 – The Mathematical Library
  • 19 – The Table Library
  • 20 – The String Library
  • 21 – The I/O Library
  • 22 – The Operating System Library
  • 23 – The Debug Library
  • 24 – An Overview of the C API
  • 25 – Extending your Application
  • 26 – Calling C from Lua
  • 27 – Techniques for Writing C Functions
  • 28 – User-Defined Types in C
  • 29 – Managing Resources

see also

reference