루아에서는 테이블에 메타테이블(metatable)이라는 것을 붙일 수 있다. 이 메타테이블에 미리 정해진 필드가 채워져 있다면 이것에 의해서 메타테이블이 붙어 있는 원래 테이블의 동작(특성)을 바꿀 수 있다.

  어떤 테이블에 메타테이블을 붙이는 것은 setmetatable()이라는 함수를 사용한다.



          setmetatable(tA, mtA) -- 테이블 tA에 메타테이블 mtA를 첨가

          tB = setmetatable({}, mtA) -- 빈 테이블에 메타테이블을 mtA를 첨가한 것을 tB에 반환


메타테이블에는 정해진 문자열 키값을 갖는 테이블을 필드로 가져야 되는데 이 미리 정해진 문자열 키값들은 __index, __newindex, __call, __tostring, __add 등등이 있다.

  이 중에서 __index 에 대해서만 간단히 설명하면 다음과 같다. 만약 테이블 tA 의 키값으로 요소들을 접근한다고 할 때(tA.nA, tA[1], tA[“FuncA”] 등등) 그 키값이 tA에 없을 때에는 nil 을 반환할 것이다. 하지만 tA에 메타테이블이 연결되어 있다면 그 연결된 메타테이블의 __index 테이블에 등록된 필드를 추가로 검사한다. 예를 들어서



          local tA={x=10}

          print(tA.y) -- "y"라는 키값이 없으므로 nil 이 찍힌다.

          local mt = { __index = { y = 20 } }

          setmetatable(tA, mt)
          print( tA.y ) -- 메타테이블에 있는 20이 찍힌다.



메타테이블의 __index 내부에는 함수도 물론 정의할 수 있다.



          local mtIndex = {}

          function mtIndex:sum()
                    return self.x + self.y — self는 메타테이블이 붙은 원래 테이블

          end

          local tA={x=10, y=20}
          setmetatable( tA, { __index=mtIndex } )

          print( tA:sum() ) -- 30이 찍힌다


          local tB = setmetatable( {x=30, y=40}, {__index = mtIndex} )
          print( tB:sum() ) -- 70이 찍힌다



위의 예제는 간단하지만 이것을 이해했다면 루아(코로나)에서 객체지향을 간단하게나마 구현하는데 응용할 수 있다.


Posted by 살레시오

  루아는 C언어와 다르게 true, false 값만을 가지는 부울형이 있다. C언어에서는 내부적으로 0값을 거짓(false)으로 취급하지만


     ‘루아는 nil 과 false 만을 거짓으로 간주한다


는 점을 유의해야 한다. 정수 0도 루아에서는 진리값이 true이다. C언어에 익숙했다면 참 헷갈리기 쉬운 부분이다.


이 사실을 염두에 두고 루아의 논리연산자를 살펴보자. 루아의 논리연산자는 and, or, not 세 가지가 있다. 각각의 동작을 정리하면 다음과 같다.


  • not A - A가 거짓이면 (즉 nil 혹은 false이면) true, 아니면 false 반환
  • A and B — A가 거짓이면 (즉 nil 혹은 false이면) A를 바로 반환, A가 참이면 B를 반환
  • A or B — A가 참이면 (즉 nil도 false도 아니면) A를 바로 반환, A가 거짓이면 B를 반환


A and B 연산에서 A가 거짓이면 B는 아예 보지도 않고(계산도 안하고) 바로 A를 반환하며, 반대로 A or B 연산에서는 A가 참이면 B는 아예 보지도 않고 바로 A를 반환한다. 이것을 조금만 생각해 보면 not은 항상 부울값(true, false)을 반환하는데 반해서 and와 or는 그 반환값이 부울값이 아닐 수도 있다는 것을 알 수 있다. 몇 가지 예를 들어보면


local a = 1 and 0 — a에는 0값이 이 저장됨

local b = nil and 1 — b에는 nil 이 저장됨

local c = 1 or 0 — c에는 1값이 이 저장됨

local d = nil or 0 — d에는 0 이 저장됨


이것을 응용하면 예를 들어서 함수의 입력인자가 nil인지 아닌지를 따져서 내부변수를 다르게 초기화할 때 용이하다.


---------------------------------------------------

local function Func( xR, yR )

     local x, y

     if xR==nil then

          x = 0

     else

          x = xR

     end

     if yR==nil then

          y = 0

     else

          y = yR

     end

---------------------------------------------------


위와 같은 긴 코드를 다음과 같이 간단하게 한 줄로 줄일 수 있다.


---------------------------------------------------

local function Func( xR, yR )

     local x, y = xR or 0, yR or 0

---------------------------------------------------


또 다른 예로 어떤 테이블이 nil인지 아닌지를 따져서 그 내부 요소를 참조하고자 할 때도 유용하다. 예를 들면


---------------------------------------------------

local x = tLoc.x

---------------------------------------------------


의 경우 만약 tLoc 테이블 자체가 nil 인 경우에는 변수 x에 nil 이 저장되는 것으로 오해하기 쉬운데 실제로는 런타임 에러가 발생하고 프로그램이 멈추게 된다. 개인적으로 코로나로 코딩하면서 초기에 많이 접한 것이 이런 종류의 런타임 에러이다. (반면 tLoc 테이블 자체는 nil이 아닌데 x요소가 없는 경우에는 x에 nil 이 저장되고 런타임 에러는 발생하지 않는다.)

이러한 런타임 에러를 없애고 테이블 자체가 nil일 경우를 처리하려면 예를 들어서 다음과 같이 해야 할 것이다.


---------------------------------------------------

local x

if tLoc == nil then

     x = nil

else

     x = tLoc.x

end

---------------------------------------------------


위와 같은 코드를 and연산자를 이용하면 다음과 같이 한 줄로 바꿀 수 있다.


---------------------------------------------------

     local x = tLoc and tLoc.x

---------------------------------------------------


이와 같이 and 와 or 연산의 특성을 이용하면 프로그램을 간결하게 작성할 수 있다.


Posted by 살레시오

루아의 변수는 다음과 같이 8가지 데이터타입을 갖는다.


nil

부울값

숫자

문자열

테이블

함수

----------

코루틴

유저데이터


이 중에서 코루틴과 유저데이터는 코로나SDK와 별로 관련이 없으므로 제외하고 나머지 여섯 개 중 참조를 갖는 것은 함수와 테이블이다. 문자열은 참조가 아니라는 것에 유의해야 한다. 참조란 실제 데이터가 저장된 곳을 가리키는 주소이다. (사실 참조도 주소'값'이므로 루아는 다 값이라고 주장하기도 하지만 혼동의 여지가 있으므로 여기서는 참조와 값을 구분하도록 하겠다.)

따라서 만약 어떤 테이블을 함수의 입력파라메터로 넘길 때 참조가 넘어가므로 그것을 받아서 조작하면 원래의 테이블값도 바뀌게 된다.


┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓

    local function Func1(a, tA)

        a = 20

        tA.x = 20

    end


    local function Func2(tA)

        tA = {x=30}

    end


    local x, tX = 10 , {x=10}

    Func1(x, tX) -- x는 값이, tX는 참조가 넘어간다.

    print(x, tX.x) -- 10, 20 이 찍힌다.

    Func2(tX)

    print(tX.x) --20이 찍힌다.

┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛


위의 예에서 Func1()을 호출할 때 로컬 변수 x는 값이 넘어가고 테이블 tX는 참조가 넘어가므로 함수 호출 후 테이블의 필드만 값이 변경되었다. 반면에 Func2()내에서는 tA가 tX의 참조를 받기는 하지만 새로운 테이블의 참조로 초기화되었다. 따라서 이경우 tA는 넘어온 tX와는 전혀 별개의 것이 되어서 tX에는 아무런 영향을 끼치지 못한다. 그래서 맨 마지막 print()에서 20이 찍히는 것이다.


이와 마찬가지로 함수의 리턴값이 테이블이나 함수일 경우도 참조를 반환한다. 다음 예는 처음에는 조금 이해하기 힘들 수 있으나 한 번 살펴보도록 하겠다.


┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓

    local function Func1(xR)

        local x = xR or 0 -- 내부변수 x를 넘어온 값으로 초기화

        local y = 0

        local Func = function() print("x :"..x);end -- 여기서 x가 사용되었다.

        return y, Func -- y는 '값'을, Func는 '참조'를 반환

    end


    local y1, Func2 = Func1(10)

    local y2, Func3 = Func1(20)

    Func3() -- 20 이 찍힌다.

    Func2() -- 10 이 찍힌다.

┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛


함수 Func1()은 그 안에서 내부변수 y의 '값'과 내부변수 Func의 '참조'를 반환한다. 기본적으로 어떤 함수의 지역변수는 함수가 종료되면 사라진다는 것은 알고 있을 것이다. 변수 y는 함수가 종료되면 더 이상 참조되는 곳이 없으므로 GC의 타겟이 되어 사라질 것이다. 하지만 내부함수 Func는 그 참조가 호출된 곳의 Func2 변수를 통해서 계속 사용되므로 사라지지 않고 살아남게 된다. 그럼 내부변수 x는 어떨까? x변수는 Func안의 print()문에서 참조를 하고 있으므로 Func가 살아있는 한 x도 계속 살아있게 된다.


또 한가지 주의할 것은 Func2 와 Func3에서 참조하는 변수 x는 서로 별개의 것이라는 점이다. 따라서 처음 Func3()호출에는 20이 찍히고 맨 마지막 Func2() 함수를 호출하면 10이 찍힌다. 서로 '다른' 변수 x를 참조하고 있기 때문이다.


이것을 이해했다면 클래스를 구현할 때 외부에서는 접근할 수 없고 내부에서만 사용할 수 있는 private 변수/함수 를 구현하는데 응용할 수 있다.

Posted by 살레시오


티스토리 툴바