-
[Lua] 함수 (2)Lua 2025. 2. 17. 17:08
이전 글 ( [Lua] 함수 (1) )에서 루아에서 함수의 형태, 값 전달 및 반환과 같은 기본적인 명세를 살펴보았습니다.
이번 글에서는 루아의 함수를 조금 더 상세하게 살펴보도록 하겠습니다.
1. 1급 객체 (First-class object)
더보기함수는 특정 기능을 수행하는 명령의 집합이라고 볼 수 있습니다.
하지만 루아의 함수는 정수, 문자열 등의 다른 값들과 동일하게 "값" 으로 취급됩니다.
예를 들어, 아래 두 줄의 코드는 동일합니다.
function foo () print("foo") end foo = function () print("foo") end
특정한 이름을 가진 변수를 만들어 값을 대입하여 사용할 때, 그 이름은 값이 아닌 변수에 주어지는 이름입니다.
객체 지향 프로그래밍에서 객체에 이름이 붙는 것 과는 다르게, 대입되는 값에는 이름이 없습니다.
이러한 개념을 익명성이라고 합니다.
위 예제를 통해, 함수도 이와 같이 익명 요소라는 것을 볼 수 있습니다.
1. 문자열 foo를 출력하는 함수 foo
2. 문자열 foo를 출력하는 함수가 저장된 변수 foo두 정의는 비슷하게 보이지만 전혀 다른 내용이며, 루아의 함수는 2번에 해당한다고 볼 수 있습니다.
함수가 값으로 취급되며, 해당 값에는 이름이 붙지 않기 때문에, 아래와 같은 코드를 작성할 수도 있습니다.
summary = { {name="foo", score=3}, {name="bar", score=2}, {name="baz", score=6}, {name="qux", score=1} } for i, v in pairs(summary) do print(v.name) end -- foo bar baz quz table.sort(summary, function (a, b) return (a.score > b.score) end) for i, v in pairs(summary) do print(v.name) end -- baz foo bar qux
위 예제는 summary의 score를 내림차순으로 정렬하는 코드입니다.
table.sort함수는 테이블을 정렬하는 함수입니다.
이 함수는 두 번째 인자로 정렬하는 방식을 정하는 함수를 전달받아, 원하는 방식의 정렬이 가능합니다.
함수는 반환값으로 사용될 수 있습니다.
foo = function (arg) return function () print(arg) end end bar = foo("Hello world!") bar()
함수 foo는 전달받은 인자를 출력하는 함수를 반환하는 함수입니다.
함수가 값으로 취급되기 때문에 특정 함수를 만들어 반환값에 전달하는 구성이 가능합니다.
위 예제들을 통해 다음과 같은 특징을 살펴보았습니다.
1. 함수는 특정한 변수에 할당될 수 있습니다.
2. 함수는 매개변수로써 전달될 수 있습니다.
3. 함수는 값으로써 반환될 수 있습니다.위와 같은 특징을 가지는 객체를 1급 객체 (First-class object)라고 합니다.
루아의 함수는 1급 객체입니다.
2. 클로저
더보기함수는 자기만의 유효 범위를 갖습니다.
함수 뿐만 아니라 여러 문법들도 그러합니다.
해당 유효 범위 안에서 할당된 변수는 일반적으로 외부에서 접근이 불가합니다.
function foo () local bar = "Hi" print(bar) end foo() print(bar) -- Hi -- nil
이와 같이, foo내부의 변수 bar는 외부에서 접근이 불가합니다.
이번엔 다른 예제를 살펴보도록 하겠습니다.
function func (arg) local foo = arg return function () print(foo) end end bar = func("Hello world!") baz = func("Hello Lua!") bar() baz() -- Hello world! -- Hello Lua!
func는 특정 익명 함수를 반환합니다.
이 함수는 func의 지역 변수를 출력하는 함수입니다.
함수가 반환되면 func의 지역 변수 foo의 유효 범위를 벗어나게 됩니다.
하지만 결과를 보면 외부 변수인 bar과 baz가 func의 지역 변수를 정상적으로 출력함을 볼 수 있습니다.
위와 같이 함수가 자신이 필요한 비 지역 변수를 포함하는 개념을 클로저라고 합니다.
위 예제의 경우, bar과 baz는 서로 다른 클로저입니다.
3. 사실은 전부 다 클로저
더보기루아의 기본 라이브러리에 있는 함수들은 모두 클로저입니다.
예를 들어, 출력으로 사용하는 print는 다음과 같이 되어있다고 볼 수 있습니다.
print = function (...) -- Code end
따라서, 다음과 같이 기본 내장 함수를 재정의 하는 것 또한 가능합니다.
print = function (...) end print("Hello world!") -- 출력 없음
위 코드는 print함수를 재정의하여, 아무 동작도 하지 않는 코드로 만든 모습입니다.
이 경우, print는 출력 함수로 사용될 수 없습니다.
이 특징을 살리면, 기본 내장 함수의 기능을 변경하는 것이 가능합니다.
do local oldPrint = print print = function (arg) oldPrint("NEW") oldPrint(arg) end end
print("Hello world!) -- NEW -- Hello world!
oldPrint("Hello world!) -- {Error}
위 코드는 기존 print를 oldPrint로 옮기고, print는 NEW라는 문자열과 함께 기존 print(oldPrint) 를 호출하는 코드입니다.
해당 코드가 do-end의 범위로 지정되어, 외부에서는 oldPrint의 접근이 불가합니다.
위 예제의 print를 다른 기능 (File IO, 네트워크 등)에 적용할 경우, 외부와 독립된 샌드박스 환경의 구현 등이 가능해집니다.
4. 꼬리 호출 (Tail call)
더보기Tail call은 함수의 마지막 코드가 다른 함수를 호출하는 형태를 의미합니다.
-- Tail call function foo (arg) return bar(arg) end -- Not tail call function foo (arg) return bar(arg) + 1 end function foo (arg) return (bar(arg)) end function foo (arg) return bar(arg) + baz(arg) end
프로그래밍 언어에서 함수가 호출될 때, 함수를 호출한 지점의 위치를 저장합니다.
함수의 실행이 종료될 경우, 저장한 정보를 기반으로 호출한 위치로 돌아와서 프로그램이 진행됩니다.
하지만 위와 같은 꼬리 호출의 경우, bar이 호출될 때 foo의 위치를 저장할 필요가 없습니다.
bar는 종료될 때 foo의 위치가 아닌, foo를 실행한 상위 호출 지점으로 돌아가면 됩니다.
이는 추가적인 메모리 사용이 불필요하다는 의미이며, 이를 꼬리 호출 제거 (Tail call elimination)라고 합니다.
꼬리 호출 제거는 최적화와 관련되어 있는 주제입니다.
이번 문단에서는 다음과 같이 정리하며, 이후 자세한 내용으로 추가적인 글을 작성할 수 있도록 하겠습니다.
1. Lua는 꼬리 호출 제거를 지원합니다.
2. 위 예제와 같이 마지막 코드가 함수의 호출인 경우에만 꼬리 호출이 됩니다.
3. 위 예제의 꼬리 호출이 아닌 이유는 다음과 같습니다.
3-1. bar의 결과값에 덧셈 연산을 수행해야 합니다.
3-2. bar의 결과가 다중 반환일 경우, 한 개만 반환해야 합니다.
3-3. bar를 호출하고, baz를 호출해서 덧셈 연산을 수행해야 합니다.
루아의 함수를 조금 더 자세하게 살펴보았습니다.
이번 글이 도움이 되셨기를 바랍니다.
감사합니다.
'Lua' 카테고리의 다른 글
[Lua] 코루틴 (Coroutine) (0) 2025.02.25 [Lua] 반복자 (Iterator)와 일반 for문 (0) 2025.02.19 [Lua] 함수 (1) (0) 2025.02.13 [Lua] 제어 구문 (if, while, repeat, for, break, goto...) (0) 2025.02.05 [Lua] 다중 할당문 (Multiple assignments) (1) 2024.10.08