루아 테이블(Table) 1 | 루아 프로그래밍 4

루아 테이블(Table)

루아의 가장 큰 특징이라고 말할 수 있는 테이블(Table) 입니다. 테이블이라는 단어가 주는 혼란을 충분히 느끼시길 바랍니다. 그건 루아의 창시자가 의도한 바로 보입니다.

루아의 창시자 중 한명인 호베르토가 쓴 루아 교재에는 table 에 대하여 여러가지 의미를 부여하고 있습니다.

루아 테이블
루아 테이블

테이블은 루아의 대표적(사실 유일한)인 자료구조 메카니즘이다. 테이블은 배열, 집합, 레코드, 등 다양한 자료구조이다. 테이블은 핵심적으로 보면 연관된 배열이다. 테이블은 인덱스로 숫자 뿐 아니라 문자열이나 다른 값도 받아들인다.

… 등 전혀 통일되지 않은 수많은 설명을 나열한 후에 결국 루아의 테이블은 값이나 변수가 아니다. 그것은 객체다. 동적 할당 받은 객체(자바와 같은)와 같다. 테이블을 조작하기 위해 프로그램은 참조(혹은 포인터)를 사용한다. 라고 설명합니다. 이게 대체 무슨 말일까요??

루아의 창시자는 이것을 객체(Object)라고도 했지만 온라인 커뮤니티에서는 루아에는 진정한 의미의 객체가 없다고 이야기하는 사람도 종종 볼 수 있습니다.

여기까지 뒤져보면 헷갈립니다. 누구 말을 들어야 할지 모르겠군요.

결론적으로 너무 의미나 개념에 구애받을 필요가 없는 것 같습니다. 호베르토의 설명이 어느정도 힌트를 주는게 아닌가 싶습니다. 루아의 탄생 목적은 게임 스크립트가 큽니다. 현재도 대부분의 사용자들이 로블록스(Roblox) 등의 게임 개발을 목적으로 사용하고 있습니다.

루아의 자료구조를 설계할 때 너무 딱딱하지 않게 좀 더 자유롭게 표현하고 노력한 것은 분명합니다. 최근에 버전이 업데이트되기 전에 루아에는 정수형(integer) 자체가 없었습니다.

그냥 double 형을 숫자(number)라고 사용했습니다. 성능에 대한 요구사항이 늘어나자 버전 업데이트(number)라고 사용했습니다. 성능에 대한 요구사항이 늘어나자 5.3 버전 업데이트에서 드디어 정수형을 도입했습니다. 따라서 과거 스크립트에서 int를 사용하면 호환성 문제가 있습니다.

double 형을 쓴 것은 표현범위가 가장 넓기 때문이었겠죠. double 안에 64비트 정수까지 포함되는 것도 사실이니까.(하지만 정수보다 성능은 낮아집니다)

루아의 창시자들은 기계적으로 정수, 실수를 나누는 것보다 테이블을 통해서 복잡한 자료구조를 자유롭게 구현하는 것에 더 신경을 썼던 것 처럼 보입니다.

그럼 Lua 인터프리터를 열고 테이블을 시작해 보겠습니다.

테이블 만들기

테이블을 만드는 것은 변수 = { } 이면 충분합니다. 테이블을 보면 32비트 주소같은 숫자가 표시되고요. 동적 메모리라고 했으니까 힙메모리 영역 같습니다.

> table1 = { }
> table1
table: 00e3c620
> index = "a"
> table1[index] = 999
> table1[12] = "Hello Lua"
> table1["a"]
999
> index = 333
> table1[index]
nil
> table1[333]
nil
> index = 12
> table1[index]
Hello Lua
> table1["a"]
999
> table1["a"] = table1["a"] + 1
> table1["a"]
1000
>

일단 nil 이란 값은 null 과도 비슷합니다. 하지만 또 null 과는 다르니 주의합니다.

테이블은 참조라고 했습니다. 참조에 null 대신 nil 이 들어가기도 하는데 기본 자료에도 nil 이 있는 것으로 봐서는 테이블만 딱히 그런 것은 아닙니다.

스크립트를 실행하면서 느끼는 부분은 인덱스가 정해져 있지 않다는 것 입니다. C나 자바의 배열이었다면 array[인덱스] 에서 인덱스는 이미 정해져 있습니다. 인덱스의 크기가 고정되면 스택메모리에 넣을 수 있습니다. 인덱스 자체를 동적으로 바꿀 생각이라면 힙메모리를 써야겠죠.

루아도 형태는 다르지만 인덱스가 자유롭게 변하기 때문에 동적 영역이란 것을 알 수 있습니다. 말이 좋아서 동적 영역이지 이런 방식은 매우 혼란스럽기 때문에 각별히 주의해야 합니다. 이런 방식은 언어를 개발한 이들의 발상으로 보입니다.

GC (Garbage Collection)

테이블의 동적 메모리할당에 대하여 설명했습니다.

이들 메모리의 사용이 끝나면 해야할 일이 있죠? 동적타이핑은 일반적으로 메모리의 해제를 자동으로 담당하는 GC(Garbage Collector)가 있습니다. 테이블을 만들 때 메모리에 동적할당이 일어나고 테이블이 nil 이 될때 (참조가 남아있지 않을 때) 메모리는 해제됩니다.

C계열 프로그래밍을 해온 사람들에게는 매우 편하게 느껴집니다.

> tableA = { }
> tableA
table: 00e3e9c8
> tableA["bbb"] = 777
> tableB = tableA
> tableB
table: 00e3e9c8
> tableB["bbb"]
777
> tableB["bbb"] = 888
> tableA["bbb"]
888
> tableA = nil
> tableB
table: 00e3e9c8
> tableB = nil
> tableA
nil
> tableB
nil

위에서는 테이블을 테이블에 할당합니다. 실제 테이블의 값을 할당하는게 아니라 참조를 할당하기 때문에 얕은 복사(shallow copy) 정도 부를 수 있습니다. 어쨋든 얕은 복사를 해서 원본을 nil 시키더라도 참조가 되있으면 메모리를 점유합니다. 참조변수의 모든 사용이 끝났을 때 비로소 GC 가 메모리를 해제합니다.

물론 이 과정이 개별적으로 보이지 않기 때문에 메모리 모니터를 사용하는 등의 방법으로 디버깅할 수 있습니다.

테이블 인덱스

배열이라고 하지만 인덱스 사용법도 다릅니다.

예를 들어 아래와 같이 for 루프로 배열을 만들었습니다.

> tableD = { }
> for i = 1, 10 do tableD[i] = i * 3 end
> tableD[0]
nil
> tableD[1]
3
> tableD[2]
6
> tableD[3]
9
> tableD[10]
30
> tableD[11]
nil

배열의 전체 크기를 명시하지 않아도 알아서 고무줄 처럼 잘 늘어나고, 심지어 다른 언어에서는 index out of range 오류 상황에도 nil 을 출력합니다. 이 말은 배열은 늘려놓고 값이 없으니 nil 을 반환한다는 의미입니다. 즉 range 를 벗어난게 아니다.

루아의 테이블은 범위를 넘나드는 가변적 유연성을 보여줍니다.

아래는 인덱스를 문자열로 만들었을 경우입니다. 도트연산자 . 로 사용할 수 있습니다. 언뜻보면 자바에서 멤버 변수에 접근하는 것과 비슷하게 보입니다. 호베르트는 이렇게 만든 것은 사용이 쉽도록 (syntactic sugar) 만든 것이라고 합니다.

> myTable = { }
> myTable
table: 00e3e860
> myTable["a"] = 222
> myTable["b"] = 333
> myTable["c"] = 555
> myTable.a
222
> myTable.b
333
> myTable.c
555

여기서 주의할 점은 “c” 라는 테이블의 인덱스와 c 라는 변수는 다르기 때문에 myTable[“c”], myTable.c 는 같은 것이고 myTable[c] 는 다른 것입니다.

그렇습니다. 많이 헷갈립니다.루아는 테이블이라는 독특한 체계를 갖고 있습니다. 이것이 개발자들의 의도라고 합니다.

배열을 오랫동안 사용해온 사람들은 어떤 한정되어 있는 아이디어를 갖습니다. 인덱스는 0부터 XX 까지 스택메모리에 어떻게 들어간다. 한편 테이블은 배열의 인덱스로 정수, 실수, 문자열 등 어떤 값이라도 사용해라. 심지어 테이블의 인덱스로 테이블을 넣어도 됩니다.(정확히는 테이블의 참조변수나 요소가 됨)

이런 방식은 자유도가 높은 만큼 민감합니다. 모든 것을 꼼꼼히 체크해야 인터프리터 오류 뿐 아니라 시멘틱 오류(semantic error)까지 잡아낼 수 있습니다.

예를 들어 2.0과 2는 같은 인덱스 입니다. 왜냐하면 2.0 은 double에서 int 형으로 자동 캐스트하기 때문입니다. 한편 2.0과 2.3은 다른 인덱스 입니다.

> a[2.0] = 10
> a
table: 00e3e7e8
> a[2.0]
10
> a[2]
10
> a[2.3] = 20
> a[2.3]
20
>

요약

루아 테이블에 대한 개요입니다.

호베르토의 설명처럼 테이블은 루아에서 가장 핵심 아이디어를 차지하고 있기 때문에 좀 더 많은 내용이 필요해서 포스팅을 나누도록 합니다.

외부참조

Programming in Lua : 2.5 루아 테이블 공식 문서

루아(Lua) 튜토리얼 7 | 테이블(tables)

Leave a Comment