클로저(Closure)
클로저(Closure) 란?
해당 강좌는 CommandHelper 의 기본적인 코딩 이해, 배열 이해 등을 했다는 것을 전제로 작성되었습니다. 커맨드헬퍼를 잘 모르시는 분은 시작하기 문서를 통해 기본을 먼저 익히시고 이러한 개념을 익히시는 것을 권장합니다.
클로저는 객체(문자열, 배열, 숫자와 같은 것)의 일종으로, 특정한 값을 담은 것이 아닌 실행할 수 있는 코드를
객체로 만든 것입니다.
위의 그림과 같이, 클로저 안에 코드를 담고, 이를 execute 펑션을 이용하여 실행하면
클로저 안에 있는 코드를 그대로 실행하는 것이 됩니다. 또한 클로저를 일반 객체처럼 다루면 코드를 그대로 문자열로 변환한 것이 됩니다.
예제
execute(closure(msg('message')))
결과: "message" 라는 메시지가 실행한 플레이어에게 전송됨.
msg(closure(msg('message'))
결과: "msg('message')" 라는 메시지가 실행한 플레이어에게 전송됨.
클로저의 인수 사용
클로저는 코드를 실행하는 객체라는 것을 이해하셨을 겁니다.
코드를 클로저라는 공간에 담아두고, 변수로 저장하여 원할 때마다 실행할 수 있는 것이죠.
하지만 만약 클로저 내부에서 ivar(@ 변수) 를 사용하고 싶다면 어떻게 해야 할까요?
몇가지 예제와 함께 살펴보겠습니다.
@player = 'ITSTAKE'
@closure = closure(
@message = '잇스테이크님 안녕하세요!'
tmsg(@player, @message)
)
execute(@closure)
결과: @player 라는 변수는 정의되지 않기 때문에 Exception 발생.
위에 예제는 클로저 속에서 @player 라는 변수와 @message 라는 변수를 이용하여 ITSTAKE 라는 플레이어에게 "잇스테이크님 안녕하세요!"
라는 메시지를 보내려고 시도하는 코드입니다.
하지만 클로저 밖에서 정의된 변수는 클로저 속에서 사용할 수 없기 때문에 오류가 발생합니다.
왜냐하면 클로저는 객체처럼 동적 실행이 가능하기 때문에, 만약 클로저를 @player 가 없는 곳으로 옮긴다면 실행이 불가능하기 때문이죠.
클로저 밖의 변수 사용하기
클로저는 이러한 문제점을 보완하기 위해 execute 펑션을 통한 외부 변수 삽입을 지원합니다.
마치 커맨드헬퍼에서 명령어에서 인수를 받을 위치에 $var(final variable) 을 사용하는 것처럼, 클로저에도 클로저를 만들 때 인수를 @var(ivarable) 을 통해 설정하거나 @arguments 배열을 통해 인수를 마음대로 받을 수 있습니다.
예제 1.
@player_outside = 'ITSTAKE'
@closure = closure(@player_in_closure,
@message = @player_in_closure'님 안녕하세요!'
tmsg(@player_in_closure, @message)
)
execute(@closure, @player_outside)
결과: ITSTAKE 라는 플레이어에게 "ITSTAKE 님 안녕하세요!" 라는 메시지가 전송됨.
위에 예제에서 보이듯이, closure 에서 코드를 작성하기 전에 인수를 먼저 지정(@player_in_closure) 하고
execute 펑션에서 클로저 객체 다음 인수에서 지정한 인수 자리에 맞는 곳에 변수를 넣어 주면(@player_outside) 해당 인수의 자리가
클로저에서의 변수 자리에 맞게 클로저 안에서 사용할 수 있습니다.(즉 @player_outside 가 execute 를 통해 @player_in_closure 로 삽입됨)
예제 2.
@player1 = 'ITSTAKE'
@player2 = 'EATSTEAK'
@closure = closure(@player1, @player2,
@message = @player2'님의 메시지:'@player1'님 안녕하세요!'
tmsg(@player1, @message)
)
execute(@closure, @player1, @player2)
@player1 = 'ITSTAKE'
@player2 = 'EATSTEAK'
@closure = closure(
@message = @arguments[1]'님의 메시지:'@arguments[0]'님 안녕하세요!'
tmsg(@arguments[0], @message)
)
execute(@closure, @player1, @player2)
결과: 두 예제 다 ITSTAKE 라는 플레이어에게 "EATSTEAK 님의 메시지: ITSTAKE 님 안녕하세요!" 라는 메시지가 전송된다.
첫번째 예제에서는 여러개의 인수를 넣어 클로저에 보냈고, 두번째 예제에서는 closure 에서 인수를 미리 정의하지 않고 @arguments 배열을 통해서 메시지를 보냈습니다.
클로저의 반환 값
클로저에서는 return() 펑션을 통해 클로저의 결과를 외부로 보낼 수 있습니다.
예제
@closure = closure(@player,
if(@player == 'ITSTAKE') {
return("찐")
} else if(@player == 'EATSTEAK') {
return("짭")
}
)
msg(execute(@player, 'ITSTAKE'))
msg(execute(@player, 'EATSTEAK'))
msg(execute(@player, 'UNKNOWN'))
결과: 플레이어에게 "찐", "짭", ""(빈 메시지) 가 순서대로 출력됨.
이 예제로 보아 closure 에서 return 을 이용하여 반환한 값은 execute 의 반환한 값과 똑같고, 클로저가 아무것도 반환하지 않았을 시 빈 값을 반환하는 것을 알 수 있습니다.
펑션에서의 클로저 사용
어떤 펑션은 펑션의 인수에 클로저를 사용하는 펑션이 있습니다. 이 문단에서는 이러한 펑션들의 예제를 통해 펑션에서 클로저를 사용하는 법에 대해 살펴봅니다.
이 펑션은 배열의 각 값마다 테스트를 하여 그 결과가 모두 참이면 true 를 반환하고 아니라면 false 를 반환합니다. 하지만 테스트의 형식이 클로저는 각 값을 하나씩 받으며, 모두 boolean 을 반환해야 한다고 합니다.
예제 1.
@array1 = array('ITSTAKE', 'EATSTEAK', 'ITSTAKE')
@array2 = array('ITSTAKE', 'EATSTEAK', 'UNKNOWN')
@closure = closure(@player,
if(@player == 'ITSTAKE' || @player == 'EATSTEAK') {
return(true)
}
return(false)
)
msg(array_every(@array1, @closure))
msg(array_every(@array2, @closure))
결과: 실행한 플레이어에게 "true", "false" 라는 메시지가 차례로 전송됨.
array_every 의 설명에 따르면 closure 가 배열의 값을 각각 하나하나씩 받는다는 것을 통해 우리는 closure 의 인수가 배열의 값 하나 라는 것을 알 수 있습니다. 그리고 받는 배열의 값을 클로저 안에서 @player 로 지정했습니다. 또한 해당 클로저는 무조건 true 혹은 false 를 반환해야 하기 때문에 return 함수를 이용해 조건문에서 true 혹은 false 를 반환하도록 하였습니다. 즉, 펑션 설명에서 클로저가 받는 인수, 클로저가 반환해야 하는 값 두가지만 알면 해당 클로저를 어떻게 사용해야 하는지 알 수 있습니다.
예제 2.
이 펑션은 array 내부의 값을 전부 더해서 하나의 값으로 만듭니다. 클로저의 인수는 이전의 계산된 값, 다음 배열 값이고, 두 값을 이용하여 계산을 한 것을 반환값으로 받습니다.
@array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
@closure = closure(@calculated_value, @next_value,
msg('calval:'@calculated_value', nextval:'@next_value)
return(@calculated_value + @next_value)
)
msg(array_reduce(@array, @closure))
결과: 실행한 플레이어에게
calval:1 , nextval: 2
calval: 3 , nextval: 3
calval: 6 , nextval: 4
calval: 10, nextval: 5
calval: 15, nextval: 6
calval: 21 , nextval: 7
calval: 28 , nextval: 8
calval: 36, nextval: 9
calval: 45, nextval: 10
55
라는 메시지가 전송됨.
주의점
클로저는 메모리에서만 존재할 수 있는 객체이기 때문에, store_value 나 json_encode 같은 영구 저장이나 직렬화가 불가능합니다. (import/export 는 사용 가능)