1297.10 순수 함수 (Pure Function) 원칙
1. 순수 함수의 정의
순수 함수(pure function)는 함수형 프로그래밍(functional programming)에서 유래한 개념으로, 다음 두 가지 성질을 동시에 만족하는 함수를 의미한다(Hughes, 1989):
-
결정론적 출력(Deterministic Output): 동일한 입력이 주어지면 항상 동일한 출력을 반환한다. 함수의 반환값은 오직 입력 매개변수에 의해서만 결정되며, 함수 외부의 상태, 호출 시점, 호출 횟수에 의존하지 않는다.
-
부작용 부재(No Side Effects): 함수의 실행이 함수 외부의 어떠한 상태도 변경하지 않는다. 전역 변수의 수정, 파일 쓰기, 네트워크 통신, 하드웨어 제어 등을 수행하지 않는다.
형식적으로 순수 함수 f는 다음과 같이 정의된다:
\forall x \in \mathcal{D}: f(x) = f(x)
여기서 \mathcal{D}는 함수의 정의역이며, 동일한 입력 x에 대한 반복적 호출이 항상 동일한 결과를 산출한다.
2. 조건 노드에 대한 순수 함수 원칙의 적용
조건 노드의 tick() 메서드는 순수 함수와 동등한 특성을 가져야 한다. 조건 노드의 입력은 블랙보드 포트를 통해 수신되는 값과 외부에서 비동기적으로 갱신되는 관측 데이터이며, 출력은 SUCCESS 또는 FAILURE의 반환 상태이다.
블랙보드의 동일한 값과 동일한 관측 데이터가 주어지면, 조건 노드는 항상 동일한 반환 상태를 산출해야 한다. 그리고 tick() 메서드의 실행이 블랙보드, 외부 시스템, 하드웨어 등의 상태를 변경해서는 안 된다.
// 순수 함수 원칙을 따르는 조건 노드
BT::NodeStatus IsValueInRange::tick()
{
double value, min_val, max_val;
getInput("value", value);
getInput("min", min_val);
getInput("max", max_val);
// 동일한 입력 → 동일한 출력, 부작용 없음
return (value >= min_val && value <= max_val)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
3. 순수 함수 원칙의 실무적 의의
3.1 캐싱 안전성
순수 함수는 캐싱(caching)을 안전하게 적용할 수 있다. 동일한 입력에 대하여 결과가 항상 동일하고 부작용이 없으므로, 이전 평가 결과를 재사용하여도 시스템의 정확성에 영향을 미치지 않는다.
BT::NodeStatus CachedCondition::tick()
{
double current_value;
getInput("value", current_value);
// 입력이 변경되지 않았으면 캐시된 결과 반환
if (current_value == cached_input_)
{
return cached_result_;
}
// 순수 함수이므로 캐시 갱신이 안전
cached_input_ = current_value;
cached_result_ = evaluate(current_value);
return cached_result_;
}
순수 함수가 아닌 조건 노드에 캐싱을 적용하면, 캐시된 결과가 현재 시점의 정확한 결과와 다를 수 있으며, 캐시된 동안 발생해야 할 부작용이 누락되는 문제가 발생한다.
3.2 테스트 용이성
순수 함수는 단위 테스트에서 가장 검증하기 쉬운 구성 요소이다. 입력을 설정하고 출력을 검증하는 것만으로 함수의 정확성을 완전히 확인할 수 있다. 외부 시스템의 상태를 설정하거나, 부작용의 발생 여부를 추적하거나, 테스트 후 상태를 복원하는 등의 추가적인 절차가 불필요하다.
TEST(ConditionTest, PureFunctionProperty)
{
auto blackboard = BT::Blackboard::create();
IsValueInRange condition("test", config);
// 동일한 입력에 대한 반복 테스트
blackboard->set("value", 5.0);
blackboard->set("min", 0.0);
blackboard->set("max", 10.0);
for (int i = 0; i < 100; i++)
{
EXPECT_EQ(condition.executeTick(), BT::NodeStatus::SUCCESS);
}
// 입력 변경 시 결과 변경 확인
blackboard->set("value", 15.0);
EXPECT_EQ(condition.executeTick(), BT::NodeStatus::FAILURE);
}
3.3 병렬 실행 안전성
순수 함수는 외부 상태를 읽거나 쓰지 않으므로, 복수의 스레드에서 동시에 호출하여도 경쟁 조건(race condition)이 발생하지 않는다. Parallel 제어 노드 하위에서 복수의 조건 노드가 동시에 평가되는 경우, 순수 함수 원칙을 따르는 조건 노드는 추가적인 동기화(synchronization) 없이 안전하게 동작한다.
3.4 추론 가능성
순수 함수의 결과는 입력에 의해서만 결정되므로, 프로그래머가 조건 노드의 동작을 추론(reason about)하기 쉽다. 조건 노드의 결과를 예측하기 위해 전역 상태, 호출 이력, 타이밍 등을 고려할 필요가 없으며, 오직 입력 포트의 값만 확인하면 된다.
4. 순수 함수 원칙의 제한과 현실적 고려
4.1 외부 상태에 대한 관측
엄밀한 의미에서 센서 값이나 토픽 메시지를 참조하는 조건 노드는 순수 함수가 아니다. 동일한 블랙보드 입력이라도 외부 환경이 변경되면 결과가 달라질 수 있기 때문이다. 그러나 행동 트리의 맥락에서 순수 함수 원칙은 다음과 같이 완화된 형태로 적용된다:
- 조건 노드는 tick 시점에서의 관측 상태에 대한 순수 함수로 동작한다. 즉, tick 시점에 읽혀진 입력값이 동일하면 결과도 동일하다.
- 외부 환경의 변화는 입력값의 변화로 반영되며, 조건 노드 자체가 환경을 변경하지 않는다.
이러한 완화된 정의 하에서, 비동기적으로 갱신되는 센서 데이터를 참조하는 조건 노드도 순수 함수 원칙을 준수할 수 있다.
4.2 시간 의존적 조건
타임아웃이나 경과 시간을 확인하는 조건 노드는 시간이라는 외부 상태에 의존한다. 이 경우 시간 정보를 블랙보드를 통해 입력으로 받거나, 시스템 시계를 “관측 가능한 외부 상태“로 간주하여 순수 함수 원칙의 완화된 형태를 적용한다.
// 시간 정보를 입력으로 받는 방식 (더 순수함)
BT::NodeStatus IsTimeoutExpired::tick()
{
double elapsed_time;
getInput("elapsed_time", elapsed_time);
double timeout;
getInput("timeout", timeout);
return (elapsed_time > timeout)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
5. 순수 함수 원칙 위반의 징후
다음의 구현 패턴은 순수 함수 원칙 위반의 징후이다:
-
내부 카운터 또는 플래그:
tick()호출마다 증가하는 카운터, 이전 호출 결과에 의존하는 플래그 등은 결정론적 출력 성질을 위반한다. -
출력 포트 사용: 조건 노드가
setOutput()을 호출하여 블랙보드에 값을 쓰는 것은 부작용에 해당한다. -
전역 변수 수정: 조건 노드가 클래스 멤버 변수나 전역 변수를 변경하는 것은 부작용이다. 단, 캐싱 목적의
mutable변수는 예외로 허용될 수 있으나, 관측 가능한 동작(observable behavior)이 변경되어서는 안 된다. -
난수 생성: 조건 평가에 난수를 사용하면 동일한 입력에 대한 결정론적 출력이 보장되지 않는다.
6. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Hughes, J. (1989). Why functional programming matters. The Computer Journal, 32(2), 98-107.
- Faconti, D., & Colledanchise, M. (2022). BehaviorTree.CPP Documentation. https://www.behaviortree.dev/
version: 0.1.0