Learn Lua in Y minutes

Page content

이 글은 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