1. 시작하며

코딩을 취미로 접한 게이들은 "함수"는 뭔지 알아도 "프로시저"는 뭔지 잘 모를 거임. 보통의 경우 두 용어에 대해 차이를 그렇게 크게 두지는 않는데, 정확히 차이는 있음. mcfunction에서 사용되는 function의 경우 "프로시저"에 더 가까운 모습을 보임. 그 이유는 바로 다음과 같음.


종류

프로시저

함수

공통점

여러 명령어의 묶음, 작업이 끝나면 호출된 주소의 다음 주소로 돌아가야 함.

차이점

바깥의 변수를 수정하는 방식으로 동작하기 때문에, 거듭 동작시켰을 때 부작용(side-effect) 이 있음

바깥의 변수를 stack-frame에 push해 놓고, 명령어의 순서에 따라 계산 뒤 return값을 반환함 이후 pop.

(push : 맨 위에 값을 놓기

pop : 맨 위의 값 제거)


예를 들어 보겠음. mcfunction으로 작성된 "프로시저"를 보겠음.


project :

add.mcfucntion :

scoreboard players operation #a int += #b int


main.mcfunction :

scoreboard players set #a int 10

scoreboard players set #b int 12

function project:add

function project:add


함수의 경우, 두 번의 function project:add 호출에 대해 같은 값 (10+12)를 내놓아야 하며, #a int 값도 변해서는 아니되나, 다른 값 (첫 번째에서는 22, 두 번째에서는 34)을 내놓을 뿐만 아니라 #a int 또한 function project:add 호출에 따라 값이 바뀌는 것을 볼 수 있음. 이렇게 값이 바뀌는 경우는 예상치 못한 버그를 생성할 뿐더러, 설상가상으로 mcfunction 파일의 이름이 조금이라도 적절하지 못하게 설정 된 경우, "어제 짜 놓은 코드를 내가 못 읽는" 재미난 상황이 벌어져 밥도 못 먹고 꼼짝없이 컴퓨터 앞에서 눈싸움을 해야 할 거임.


2. 약한 함수의 구현

그렇다면 값을 복사하면 함수를 구현할 수 있는 거 아니냐? 라고 생각한 게이들 있을 텐데, 난 그 대답에 100점 만점에 50점을 주고 싶음. "아니 니가 뭔데 짜치게 50점만 주냐"라고 생각한 게이들에게는 "그렇게 짜면 함수의 기능 중 50%만 구현하는 것"이라는 것을 말하고 싶을 뿐이라는 점을 알리고 싶음.

그러나 완전히 함수 100% 자체를 구현하는 것은 최적화 면에서 조금 떨어지는 면도 있을 뿐더러, 매크로를 사용하는 방법 이외에는 없으므로, 일단 반쪽짜리 약한 함수를 구현하는 방법에 대해 말하겠음.


project :

add_a_b.mcfucntion :

scoreboard players operation #a_param int = #a int

scoreboard players operation #b_param int = #b int

scoreboard players operation #a_param int += #b_param int

return run scoreboard players get #a_param int


main.mcfunction :

scoreboard players set #a int 10

scoreboard players set #b int 12

execute store result score #k int run function project:add_a_b

execute store result score #k int run function project:add_a_b


위의 코드의 경우, function project:add_a_b 반복 호출에 따른 #k int의 값이 변하지 않음을 확인할 수 있음.

그렇다면 100% 구현시킨 함수는 어떻게 생겼을까?


3. 강한 함수의 구현

일단 코드 먼저 보겠음.


project :

add_a_b.mcfucntion :

#매크로를 이용한 매개변수 전달

$scoreboard players set #a_param int $(a)

$scoreboard players set #b_param int $(b)

#계산

scoreboard players operation #a_param int += #b_param int

#stack-frame pop

data remove storage minecraft:stackframe stack[-1]

#리턴

return run scoreboard players get #a_param int


main.mcfunction :

#매크로를 이용한 매개변수 전달

data modify storage minecraft:stackframe stack append value {a : 10, b : 12}

execute store result score #k int run function project:add_a_b with storage minecraft:stackframe stack[-1]


data modify storage minecraft:stackframe stack append value {a : 10, b : 12}

execute store result score #k int run function project:add_a_b with storage minecraft:stackframe stack[-1]


위의 코드의 경우, function project:add_a_b 반복 호출에 따른 #k int의 값이 변하지 않음 또한 확인되었음. 하지만 새로운 데이터 구조 minecraft:stackframe stack이 생겨버렸음.

왜 갑자기 스택에다 냅다 매개변수들을 집어넣고 난리냐! 라고 생각할 수도 있겠음. 하지만 "원래 함수는 이렇게 돌아간다."왜냐하면, 재귀 호출도 염두해 두기 때문임. 다음을 보도록 하겠음.


4. 재귀 피보나치 (최악시간복잡도 : O(2^n) -> 낮은 수에서만 실행하셈...)


project :

fibonacci.mcfucntion :

#매크로를 이용한 매개변수 전달

$scoreboard players set #fibbonacci_x int $(x)

execute if score #x int matches 0 run return 0

execute if score #x int matches 1 run return 1


scoreboard players remove #fibbonacci_x int 1

data modify storage minecraft:stackframe stack append value {x : 0}

execute store result storage minecraft:stackframe stack[-1].x run scoreboard players get #fibbonacci_x int

execute store result score #tmp0 int run function project:fibonacci with storage minecraft:stackframe stack[-1]


scoreboard players remove #fibbonacci_x int 1

data modify storage minecraft:stackframe stack append value {x : 0}

execute store result storage minecraft:stackframe stack[-1].x run scoreboard players get #fibbonacci_x int

execute store result score #tmp1 int run function project:fibonacci with storage minecraft:stackframe stack[-1]


#pop

data remove storage minecraft:stackframe stack[-1]

return run scoreboard players operation #tmp1 int += #tmp2 int


main.mcfunction :

#매크로를 이용한 매개변수 전달 : json 자료형 만든 뒤, scoreboard 기준으로 수정

scoreboard players set #x int 4

data modify storage minecraft:stackframe stack append value {x : 0}

execute store result storage minecraft:stackframe stack[-1].x run scoreboard players get #x int

execute store result score #k int run function project:fibonacci with storage minecraft:stackframe stack[-1]



위의 경우에는 stackframe stack에 재귀 호출에 따라 변화된 매개 변수가 계속 push되고 pop된다. 이런 느낌으로...

(아래는 예상되는 stackframe stack의 값 양상을 보인 것임.)

[{x : 4}]

[{x : 4}, {x : 3}]

[{x : 4}, {x : 3}, {x : 2}]

[{x : 4}, {x : 3}, {x : 2}, {x : 1}]

[{x : 4}, {x : 3}, {x : 2}, {x : 0}]

[{x : 4}, {x : 3}, {x : 1}]

[{x : 4}, {x : 2}, {x : 1}]

[{x : 4}, {x : 2}, {x : 0}]

[{x : 4}]

[]

따라서 k의 값은 fibb(4)의 값인 3이 될 것임.


4. 결론

재귀 호출을 사용하는 알고리즘을 사용해야 하는 경우 -> 완전한 함수를 쓴다.

재귀 호출이 필요 없는 경우 -> 반쪽짜리 함수를 쓴다

프로시나를 사용해야 하는 경우 -> 이름을 잘 짓자. (주석을 아주 잘 달자)