Learn Lua in Y minutes
이 글은 Lua의 기본적인 문법을 소개하는 페이지를 번역한 글 입니다.
Vim을 잘 사용하기 위해 Lua를 공부해보려고 찾은 자료인데, 문법이 의식의 흐름대로 잘 정리되어있는 것 같습니다. 번역하면서 읽어야 찬찬히 읽을 수 있어서 번역을 해봅니다.
단락 별로 원문을 그대로 옮기고, 번역을 붙이는 것도 고려해보았습니다만, 코드가 충분한 부연 설명의 역할을 하고 있어서, 원문은 옮기지 않습니다.
코드를 다운 받으세요. learnlua.lua
-- 한 줄 주석
--[[
여러 줄 주석
--]]
-------------------------------------------------------------------------------
-- 1. 변수와 흐름 제어
-------------------------------------------------------------------------------
num = 42 -- 모든 숫자는 더블(double)이다.
-- 너무 놀라지 않기를. 64비트 더블은 정수 값을 담는 데 52비트를 사용한다.
-- 기계 정확도는 52비트 미만의 정수에 대해서는 문제가 되지 않는다.
s = 'walternate' -- 파이썬 처럼 불변 문자열
t = "double-quotes are also fine"
u = [[ Double brackets
start and end
multi-line strings.]]
t = nil -- 정의되지 않은 t; Lua는 가비지 컬렉션이 있다.
-- 블럭은 do/end 키워드로 표시된다.
while num < 50 do
num = num + 1 -- ++ 혹은 += 연산자는 없다.
end
-- if 조건:
if num > 40 then
print('over 40')
elseif s ~= 'walternate' then -- ~= 은 같지 않다는 것이다.
-- 파이썬처럼 == 동등 비교 연산자이다. 문자열에도 가능하다.
io.write('not over 40\n') -- 표준 출력
else
-- 전역 변수가 기본이다.
thisIsGlobal = 5 -- 캐멀케이스가 통상적이다.
-- 지역 변수는 이렇게 만든다.
local line = io.read() -- 한 줄을 입력받는 표준 입력
-- 문자열은 .. 연산자로 합친다.
print('Winter is coming, ' .. line)
end
-- 정의되지 않은 변수는 nil을 반환한다.
-- 이건 에러가 아니다.
foo = anUnknownVariable -- foo는 이제 nil 이다.
aBoolValue = false
-- 오직 nil과 false만 falsy 값이다. 0과 ''는 true이다!
if not aBoolValue then print('twas false') end
-- 'or' 와 'and' 는 short-circuit이다. (즉, 왼쪽의 조건이 true면 두번째 평가는 하지 않는다.)
-- 이건 C나 Js의 a?b:c 삼항연산자와 비슷하다.
ans = aBoolValue and 'yes' or 'no' -- 'no'
karlSum = 0
for i = 1, 100 do -- range는 양쪽 끝을 모두 포함한다.
karlSum = karlSum + 1
end
-- "100, 1, -1" 카운트 다운을 하기 위한 range
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end
-- 보통, range는 시작, 끝[, 스텝] 이다.
-- 또다른 반복문
repeat
print('the way of the future')
num = num - 1
until num == 0
-------------------------------------------------------------------------------
-- 2. 함수
-------------------------------------------------------------------------------
function fib(n)
if n < 2 then return 1 end
return fib(n - 2) + fib(n - 1)
end
-- 클로저와 익명함수도 가능하다.
function adder(x)
-- 반환되는 함수는 adder가 호출될 때 생성되는데, x값을 기억한다.
return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16)) --> 25
print(a2(64)) --> 100
-- 반환(Returns), 함수 호출, 그리고 할당 모두
-- 리스트의 길이가 일치하지 않더라도 작동합니다.
-- 일치하지 않은 receiver는 nil이 되고,
-- 일치하지 않은 sender는 버려집니다.
x, y, z = 1, 2, 3, 4
-- 이제 x = 1, y = 2, z = 3, 그리고 4는 버려집니다.
function bar(a, b, c)
print(a, b, c)
return 4, 8, 15, 16, 24, 42
end
x, y = bar('zaphod') --> prints "zaphod nil nil"
-- 이제 x = 4, y = 8 이고, 15...42는 버려집니다.
-- 함수는 일급 입니다. 지역적으로나 전역적일 수 있습니다.
-- 이 둘은 같습니다:
function f(x) return x * x end
f = function (x) return x * x end
-- 이 둘도 마찬가지 입니다.
local function g(x) return math.sin(x) end
local g; g = function (x) return math.sin(x) end
-- the 'local g' decl makes g-self-references ok.
-- Trig funcs work in radians, by the way,
-- 하나의 문자열로 호출하는 건 괄호를 생략할 수 있습니다.
print "hello" -- 작동합니다.
-------------------------------------------------------------------------------
-- 3. 테이블
-------------------------------------------------------------------------------
-- 테이블은 Lua의 유일한 복합 데이터 구조체입니다.
-- 그것들은 연관 배열(associative array) 입니다.
-- php의 배열, 자바스크립트의 객체와 유사합니다.
-- 그것들은 리스트로 사용할 수 있는 해시로 검색되는(hash-lookup) 딕셔너리 입니다.
-- 테이블을 딕셔너리나 맵으로 사용합니다.
-- 딕셔너리 리터럴은 문자열 키를 기본으로 가집니다.
t = {key1 = 'value1', key2 = false}
-- 문자열 키는 자바스크립트처럼 dot을 사용할 수 있습니다.
print(t.key1) -- 'value1'를 출력합니다.
t.newKey = {} -- 새로운 키/값 쌍을 추가합니다.
t.key2 = nil -- key2를 테이블에서 제거합니다.
-- 리터럴은 nil이 아니면 어떤 값이라도 키로 사용할 수 있습니다.
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28]) -- 'tau'를 출력합니다.
-- 기본적으로 키는 숫자와 문자열 값으로 구분된다.
a = u['@!#'] -- 이제 a = 'qbert'
b = u[{}] -- 1729를 예상했겠지만, nil 이다.
-- 찾는 데 실패했기 때문에 b = nil 이 된다.
-- 우리가 사용한 키는 새로운 객체이기 때문이다.
-- 원래 값을 저장할 때 사용하나 그것과는 다르다.
-- 그래서 문자열과 숫자가 더 유용한 키 이다.
-- 하나의 테이블을 파라미터로 갖는 함수는 괄호를 생략할 수 있다.
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'} -- 'Sonmi~451'을 출력한다.
for key, val in pairs(u) do -- 테이블 반복
print(key, val)
end
-- _G 는 전역의 특별한 테이블이다.
print(_G['_G'] == _G) -- 'true'를 출력한다.
-- 테이블을 리스트 혹은 배열처럼 사용한다.
-- 리스트 리터럴은 암묵적으로 int 키로 생성된다.
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do -- #v 는 v 리스트의 크기이다.
print(v[i]) -- 인덱스가 1부터 시작이다. 미쳤다!!
end
-- 리스트는 실제로 존재하는 타입은 아니다. v는 그냥 테이블이다.
-- 연이은 정수 키를 가지면, 리스트처럼 다뤄진다.
-------------------------------------------------------------------------------
-- 3.1 메타테이블과 메타메소드
-------------------------------------------------------------------------------
-- 테이블은 연산자 오버로딩이 가능한 메타테이블을 가질 수 있습니다.
-- 이후에 우리는 이것이 자바스크립트의 프로토타입과 같이 작동하는 것을
-- 살펴보겠습니다.
f1 = {a = 1, b = 2}
f2 = {a = 2, b = 3}
-- 이렇게 작동하지 않습니다.
-- s = f1 + f2
metafraction = {}
function metafraction.__add(f1, f2)
sum = {}
sum.b = f1.b * f2.b
sum.a = f1.a * f2.b + f2.a * f1.b
return sum
end
setmetatable(f1, metafraction)
setmetatable(f2, metafraction)
s = f1 + f2 -- f1 메타테이블의 __add(f1, f2)를 호출합니다.
-- f1, f2의 메타테이블에는 자바스크립트의 프로토타입과는 다르게
-- 키가 없습니다. 그래서 getmetatable(f1)으로 가져와야 합니다.
-- 메타테이블은 Lua가 알고 있는 키를 가진 보통의 테이블 입니다
-- __add처럼요.
-- 하지만 다음 줄의 s는 메타테이블이 없기 때문에, 실패합니다.
-- t = s + s
-- 아래와 같이 클래스와 비슷한 패턴으로 수정할 수 있습니다.
-- 메타테이블의 __index은 점(dot) 조회를 오버로드 합니다.
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal -- 작동한다, 메타테이블 고마워.
-- 다이렉트 테이블이 조회에 실패하면, 메타테이블의 __index 값을
-- 사용한다, 그리고 재귀적으로 작동합니다.
-- __index 값은 더 커스터마이즈 조회 함수를 사용할 수 있습니다.
-- function(tbl, key)
-- __index, add, ... 등을 메타메소드라고 부릅니다.
-- 메타메소드의 전체 리스트 입니다.
-- __add(a, b) for a + b
-- __sub(a, b) for a - b
-- __mul(a, b) for a * b
-- __div(a, b) for a / b
-- __mod(a, b) for a % b
-- __pow(a, b) for a ^ b
-- __unm(a) for -a
-- __concat(a, b) for a .. b
-- __len(a) for #a
-- __eq(a, b) for a == b
-- __lt(a, b) for a < b
-- __le(a, b) for a <= b
-- __index(a, b) <fn or a table> for a.b
-- __newindex(a, b, c) for a.b = c
-- __call(a, ...) for a(...)
-------------------------------------------------------------------------------
-- 3.2 클래스 같은 테이블과 상속
-------------------------------------------------------------------------------
-- 클래스는 내장되어있지 않습니다. 테이블이나 메타테이블을 이용해서 만드는
-- 여러가지 다른 방법이 있습니다.
-- 아래 예로 설명하겠습니다.
Dog = {} -- 1.
function Dog:new() -- 2.
newObj = {sound = 'woof'} -- 3.
self.__index = self -- 4.
return setmetatable(newObj, self) -- 5.
end
function Dog:makeSound() -- 6.
print('I say ' .. self.sound)
end
mrDog = Dog:new() -- 7.
mrDog:makeSound() -- 'I say woof' -- 8.
-- 1. Dog은 클래스처럼 작동합니다. 정말 테이블 입니다.
-- 2. function tablename:fn(...) 은 function tablename.fn(self, ...)
-- 와 동일합니다.
-- `:`는 단지 첫번째 인자로 self를 추가합니다.
-- 7, 8번을 읽고 self가 어떻게 값을 가져오는 지 알아보세요.
-- 3. newObj는 Dog 클래스의 인스턴스가 됩니다.
-- 4. self 는 인스턴스화된 클래스입니다. 여기서 self는 Dog의 인스턴스입니다만,
-- 상속이 이걸 변경할 수 있습니다.
-- 우리가 newObj의 메타테이블을 설정하고, self의 __index를 self에
-- 설정했을 때, newObj는 self의 함수를 가집니다.
-- 5. 다시 상기하자면, setmetatable은 첫번째 인자를 반환합니다.
-- 6. `:`은 2번에서처럼 작동하지만, 이번에 우리는 클래스 대신 self가
-- 인스턴스가 될 것을 예상합니다.
-- 7. Dog.new(Dog)과 동일하게, new()로 self는 Dog이 됩니다.
-- 8. mrDog.makeSound(mrDog)과 동일하게, self는 mrDog이 됩니다.
-------------------------------------------------------------------------------
-- 상속의 예:
LoudDog = Dog:new() -- 1.
function LoudDog:makeSound() -- 2.
s = self.sound .. ' '
print(s .. s .. s)
end
seymour = LoudDog:new() -- 3.
seymour:makeSound() -- 4.
-- 1. LoudDog은 Dog의 메소드와 변수를 가집니다.
-- 2. self는 new()로부터 'sound'키를 갖습니다.
-- 3. LoudDog.new(LoudDog)과 동일하지만, LoudDog은 new키가 없기 때문에,
-- Dog.new(LoudDog)으로변환됩니다. 그러나 메타테이블에 __index = Dog를
-- 갖습니다.
-- 결과적으로 seymour의 메타테이블은 LoudDog이고, LoudDog.__index = LoudDog
-- 입니다. 그래서 seymour.key는 seymour.key, LoudDog.key, Dog.key와
-- 동일합니다. 이 중 어떤 테이블이든 주어진 키를 갖고 있다면 우선됩니다.
-- 4. 'makeSound' 키는 LoudDog에서 발견됩니다.
-- 그것은 LoudDog.makeSound(seymour)와 동일합니다.
-- 필요하다면, 서브클래스의 new()는 base의 그것처럼 작동합니다.
function LoudDog:new()
newObj = {}
-- set up newObj
self.__index = self
return setmetatable(newObj, self)
end
-------------------------------------------------------------------------------
-- 4. 모듈
-------------------------------------------------------------------------------
--[[ 이 부분을 주석처리 하여, 스크립트의 다른 부분이 실행되도록 합니다.
-- 파일 mod.lua가 아래와 같다고 가정합니다.
local M = {}
local function sayMyName()
print('Hrunkner')
end
function M.sayHello()
print('Why hello there')
sayMyName()
end
return M
-- 또 다른 파일에서 mod.lua의 기능을 사용할 수 있습니다.
local mod = require('mod') -- 파일 mod.lua를 실행합니다.
-- require는 모듈을 포함하는 표준 방법 입니다.
-- require는 이렇게 작동합니다. (캐시가 되지 않는다면, 아래를 보세요)
local mod = (function ()
<contents of mod.lua>
end)()
-- 마치 mod.lua는 함수의 내용과 같다, 그래서 local 값들은 mod.lua 밖에서
-- 보이지 않습니다.
-- mod.lua에서 M은 여기서 mod이기 때문에 작동합니다.
mod.sayHello() -- Hrunkner에게 hello
-- sayMyName은 오직 mod.lua에만 존재하기 때문에, 이것은 틀렸습니다.
mod.sayMyName() -- 에러
-- require의 반환 값은 캐시됩니다. 그래서 여러번 require를 하더라도
-- 파일은 한 번 실행됩니다.
-- mod2.lua는 "print('Hi!')"를 가진다고 해봅시다.
local a = require('mod2') --> Hi!를 출력합니다.
local b = require('mod2') --> 출력하지 않습니다. a와 b는 동일합니다.
-- dofile은 캐시하지 않는 require와 같습니다.
dofile('mod2.lua') --> Hi!
dofile('mod2.lua') --> Hi! (다시 실행합니다.)
-- loadfile은 Lua파일을 불러오지만, 실행하지는 않습니다.
f = loadfile('mod2.lua') -- 실행하려면 f()를 호출합니다.
-- load는 loadfile의 문자열용 입니다.
-- (loadstring은 향후 없어질 것 입니다. 대신 load를 사용하세요.)
g = load('print(343') -- 함수를 반환 합니다.
g() -- 343을 출력합니다. 그 전까지는 아무것도 프린트 하지 않습니다.
--]]
참고자료
저는 Lua를 재미있게 공부했고, Love 2D game engine를 이용해서 게임을 만들 수 있었습니다. 그게 이유(why)입니다.
BlackBulletIV’s Lua for programmers를 시작했습니다. 다음에는 Programming in Lua를 읽었습니다. 그것이 방법(how)입니다.
lua-users.org의 Lua short reference를 참고하는 것도 도움이 될 것 입니다.
다루지 않은 주요 주제는 표준 라이브러리들 입니다.
전체 파일 모두 유효한 Lua 코드 입니다. learn.lua로 저장하고 “lua learn.lua"로 실행해보세요!
이건 tylerneylon.com에 처음 쓴 글 입니다. github gist에도 있습니다. Have fun with Lua!
만약 제안 혹은 수정할 게 있나요? 깃헙 레포에 이슈를 등록해주세요 혹은 Pull Request를 직접 만들어주세요!
Tyler Neylon이 처음 작성하고, 6명의 기여자가 업데이트 하였습니다.
©2022 Tyler Neylon