티스토리 뷰

매트랩 커뮤니티에 MATurtle 코드를 올렸다. 파이썬의 turtle을 매트랩에서 그대로 구현한 것이었다.

코드 리팩토링 과정에서 ChatGPT의 도움을 많이 받았는데, 가장 도움 받은 것은 클로저(closure)였다. MATurtle에서 클로저가 어떻게 쓰였는지는 나중에 따로 설명하기로 하고, 이번 글에서는 매트랩에서 클로저를 만들고 사용하는 방법에 대해 알아본다.
간단히 말하자면, 클로저는 함수(동작)에 변수(상태)를 묶어서 관리할 수 있는 유닛이다. 함수와 변수를 묶는다고 하면 클래스가 먼저 생각나지만, 매트랩에서 클래스를 만들려면 별도의 M-파일을 만들어야 하는 번거로움이 있다. 클로저는 하나의 파일 안에서 nested function을 이용해서 구현할 수 있다. 예제를 먼저 보자.
function c = counter(init)
c = @next;
function newval = next()
init = init + 1;
newval = init;
end
end
>> c = counter(10)
c =
function_handle with value:
@counter/next
>> c()
ans =
11
>> c()
ans =
12
>> c()
ans =
13
>>
함수 counter는 값 init을 입력받는다. 그리고 nested function인 next의 핸들을 반환한다. 따라서 global 변수 c를 호출할 때마다 실제로는 next가 실행된다. c가 @counter/next라고 적힌 것이 이것을 설명한다. 그런데 c()를 실행할 때마다 값이 1씩 올라간다. 변수의 scope을 잘 봐야 한다.
Nested function은 상위 함수의 scope에 접근할 수 있다. 따라서 next 안에서 init을 참조할 수 있고 변경할 수도 있다. 위 코드에서 next는 init에 1을 추가한 newval을 반환한다. 중요한 것은 상위 함수에서 만든 init이 next 안에서 참조되고 있다는 것이다. 이것을 함수 next가 변수 init을 "캡쳐"한다고 말한다. 이를 통해 변수 init이 함수 next에 묶인다(bind). counter(10)을 호출하면 init에 10이 들어간 채로 next에 묶여서 변수 c로 반환된다. 이제 c()를 호출할 때마다 init이 1씩 증가하여 반환된다.
함수 next의 구조가 어색해보일 수 있다. 특히 아래 한 줄은 불필요한 코드로 보인다.
newval = init;
그냥 init에 1을 더한 후에 바로 반환하면 되지 않을까?
function c = counter(init)
c = @next;
function init = next()
init = init + 1;
end
end
이렇게 하면 에러가 발생한다.
>> c = counter(10)
c =
function_handle with value:
@counter/next
>> c()
Unrecognized function or variable 'init'.
Error in counter/next (line 4)
init = init + 1;
>>
여기엔 미묘한 동작의 차이가 있다. Nested function을 아래와 같이 쓰면,
function c = counter(init)
c = @next;
function init = next()
init = init + 1;
end
end
함수 next에 들어가자마자 반환값이 init인 것을 보고 init을 local variable로 "인식"한다. Code analyzer가 init 밑에 밑줄을 띄워줄텐데, 여기에 마우스를 띄워보면 "Variable might be used before it is defined."라는 경고가 뜬다. init 초기화 없이 연산을 했기 때문이다. 실제로 next에 들어가자마자 breakpoint를 걸고 Workspace를 보면 init이 있는데 값을 보여줄 수 없다는 에러가 떠 있다.

init의 scope이 불분명하기 때문이다. 그래서 반환값은 다른 변수여야 한다. 이것이 굳이 아래와 같이 반환값을 새로 정의한 이유이다.
function c = counter(init)
c = @next;
function newval = next()
init = init + 1;
newval = init;
end
end
아래와 같이 초기값을 설정하고 값을 넣을 때마다 누적하려면 어떻게 할까?
>> a = adder(10);
>> a(5)
ans =
15
>> a(3)
ans =
18
>> a(-2)
ans =
16
>>
답은 아래와 같다. (클릭해서 볼 수 있다.)
function fcn = adder(val)
arguments
val = 0
end
fcn = @add;
function added = add(to_add)
val = val + to_add;
added = val;
end
end
난이도를 조금 올려보자. 클로저 함수를 만들되, 실행 횟수를 제한하고 싶다.
>> say = @() disp("Hello!");
>> l = limited(say, 2);
>> l()
Hello!
>> l()
Hello!
>> l()
Warning: limit exceeded
> In test_closure>limited/rest (line 68)
In test_closure (line 3)
>>
limited(f, N)을 실행하면 f를 N번까지만 실행하고, 그 이후는 경고를 띄우는 클로저를 반환한다. 정답은 아래와 같다.
function fcn = limited(h, N)
fcn = @rest;
function rest()
if N <= 0
warning('limit exceeded')
return
end
h()
N = N - 1;
end
end
Nested function rest를 실행할 때마다 함수 h를 실행하고 N을 1 줄인다. N이 0 이하이면 경고를 출력하고 바로 반환한다.
2개 이상의 동작을 묶어서 클로저로 만들 수도 있다. 아래와 같이 구조체 배열로 묶으면 된다.
function fcn = generator()
n = 0;
fcn = struct( ...
'next', @next, ...
'reset', @reset);
function nn = next()
n = n + 1;
nn = n;
end
function reset()
n = 0;
end
end
fcn은 2개의 함수 next와 reset을 들고 있는 클로저이다. 구조체의 field가 함수명, value가 함수핸들이므로 일반적인 구조체를 사용하듯이 사용하면 된다.
>> g = generator();
>> g.next()
ans =
1
>> g.next()
ans =
2
>> g.next()
ans =
3
>> g.reset()
>> g.next()
ans =
1
>>
이번 글에서는 매트랩에서의 클로저 구현에 대해 알아보았다.
끗
'matlab' 카테고리의 다른 글
| Matlab snow (0) | 2025.10.04 |
|---|---|
| pie 대신 piechart를 쓰자 (0) | 2025.09.26 |
| 단톡방 Bar Chart Race - 날짜별로 끊기 (0) | 2025.09.07 |
| 단톡방 채팅 건수를 Bar Chart Race로 만들어보자. (0) | 2025.08.26 |
| (펌) MATLAB vs PYTHON (0) | 2025.07.08 |