루아(Lua)의 사용자 정의 리터럴

Twitter icon류광, 2015-06-24 22:06
루아의 간결한 함수 호출 구문을 이용한 사용자 정의 리터럴 흉내내기

예전부터 C++는 리터럴에 접미사를 붙여서 리터럴의 구체적인 형식을 명시하는 표기법을 지원했습니다. 0.5f123L이 그러한 예입니다. C++11과 C++14에서는 그런 리터럴 접미사의 종류가 많이 늘어났고, 또 다양한 종류의 문자열을 구분하기 위한 문자열 리터럴 '접두사'들도 많이 생겼습니다. 이를테면 u8"UTF-8 문자열" 같은 문자열 리터럴이 있습니다.

더 나아가서, C++11에는 아예 리터럴 접미사를 사용자가 직접 만들어 쓸 수 있게 하는 사용자 정의 리터럴(user-defined literal) 기능이 도입되었습니다. 사용자 정의 리터럴을 잘 활용하면 "캐주얼한" C++ 코드의 안전성을 높일 수 있습니다. 이를테면 이런 코드가 가능합니다.

// Meter와 Mile이라는 사용자 정의 수치 형식들이 있다고 가정
Meter operator "" _meter(double len) {return Meter(len); }
Mile operator "" _mile(double len) {return Mile(len); }

template<typename T>
T hypotenuse(T b, T h); // 밑변과 높이로부터 빗변을 계산
                        // 구현은 생략
...

auto h1 = hypotenuse(3.0_meter, 4.0_meter);

auto h2 = hypotenuse(3.0_meter, 4.0_mile); // 컴파일 오류

(참고로 _meter_mile_은 필수입니다. _이 없는 접미사는 표준만 사용할 수 있습니다.)

h2의 경우 형식 불일치 때문에(좀 더 정확하게는, 컴파일러가 첫 인수와 둘째 인수의 형식이 다른 버전의 hypotenuse를 찾지 못해서) 오류가 납니다. 이렇게 하면 단위가 다른 두 수치를 함께 계산해서 생기는 논리적 버그를 미리 방지할 수 있습니다(실제로 이런 종류의 버그 때문에 큰 사고가 난 적이 있지요). 어떤 경우이든 단위 불일치에 의한 버그를 방지할 수 있습니다.

그런데 루아에도 이런 사용자 정의 리터럴 기능이 있습니다. 단, 루아의 사용자 정의 리터럴에는 접미사가 아니라 접두사가 쓰이고, 리터럴을 반드시 따옴표나 중괄호로 감싸야 합니다. 그리고 _는 필수가 아닙니다.

예:

local dec = b"1010" -- dec는 십진수로 10 (=이진수 1010)
local h = m{100.0} -- h는 100미터

루아를 제대로 공부하신 분은 "루아의 사용자 정의 리터럴"이 거짓말임을 금방 알아챘을 것입니다. 루아를 굵직한 부분만 훑고 넘어간 분이라면 루아에 이런 기능이 있었나 싶을 것이고요.

사실 b"1010"이나 m{100.0}''''''은 사용자 정의 리터럴이 아니라 그냥 함수 호출입니다. 루아에서는 함수 호출의 인수가 하나이면, 그리고 그 인수가 문자열 리터럴이나 테이블 리터럴이면, 호출 표기에서 괄호를 생략할 수 있습니다. 즉, 앞의 두 줄은 다음에서 괄호를 생략한 것입니다.

local dec = b("1010")
local h = m({100.0})

루아에는 이처럼 뭔가 복잡해 보이는 기능이 사실은 간단한 "표기 상의 요령"일 뿐인 경우가 종종 있는데, 이런 점이 루아의 매력이라고 생각합니다(루아만의 특징은 아니겠지만요). "사용자 정의 리터럴 지원 기능을 언어 자체에 추가하자"가 아니라, "호출 시 괄호를 생략할 수 있게 해서 호출 구문을 마치 사용자 리터럴처럼 사용할 수 있게 하자"인 것이죠.

리터럴 이야기를 하는 김에 한 가지 더: 루아에는 문자열 처리를 위한 string이라는 형식이 있지만, 문자열 리터럴이 그 자체로 string 객체는 아닙니다. 예를 들어 다음은 적법한 코드이지만,

local s = "hello %s"
local result = s:format("world")

다음은 구문 오류입니다.

local result = "hello %s":format("world") -- 오류!

문자열 리터럴을 string 객체로 만들려면 괄호로 한 번 감싸주면 됩니다:

local result = ("hello %s"):format("world")

문자열 리터럴 자체가 string 객체인 경우(파이썬이나 JavaScript처럼)에 비하면 여분의 괄호가 필요하지만, 앞에서처럼 변수를 따로 두거나 다음과 같이 string을 명시하는 호출보다는 타자량이 적습니다.

local result = string.format("hello %s", "world")

한편, obj.func(obj, args)obj:func(args)로 표기할 수 있다는 것 역시 앞에서 말한 루아의 매력적인 표기 상의 요령인데요. 기회가 되면 언제 한 번 글을 써 보겠습니다.


Programming In Lua 같은 책에 다 나와 있는 이 이야기를 굳이 한 것은, 조금 전에 언급했듯이 루아를 그냥 굵직한 부분만 공부하거나 좋게 말해서 사례 기반으로 공부한 분들이 좀 있는 것 같아서입니다. 아직도 그냥 게임 스크립트용으로만 쓰이는 언어라는 이미지가 강한 것 같은데, 루아는 진지하게 파고 들어가 볼만한, 꽤나 깊이가 있는 언어라고 생각합니다. 늦더라도 Programming In Lua 제3판 번역서가 나오길 기대하며 이만 줄입니다.

(2015-07-08 추가) 마지막 문장을 다음과 같이 수정합니다: Programming In Lua 제3판 번역서('프로그래밍 루아') 많이들 읽으시길 기대하며 이만 줄입니다. 출간 소식 알려주신 남수진님 감사합니다.

태그: 프로그래밍 Lua

comments powered by Disqus