learn

Statement와 Expression이란 무엇인가

Statement와 Expression이란 무엇인가

프로그래밍을 처음 배울 때 자연스럽게 형성되는 직관이 있다. *"statement는 한 줄짜리 명령이고, expression은 그 안에 들어가는 수식"*이라는 그림. 이 직관은 C나 Python을 쓸 때는 들어맞지만, 사실 더 깊은 정의가 따로 있다. 그 정의를 이해하면 what-is-expression-based-language|expression-based 언어가 왜 그런 모양을 하고 있는지도 같이 보인다.

이론적 정의

  • Expression: 평가(evaluate)되어 값을 만들어내는 것
  • Statement: 실행(execute)되어 어떤 효과(side effect)를 일으키는 것

핵심 차이는 *"값을 만드냐, 일을 시키냐"*다. "변수에 대입"이라는 건 statement가 일으키는 효과의 한 종류일 뿐, statement 자체의 정의가 아니다.

Expression: 값으로 환원되는 것

3 + 4           # 평가하면 7
"hello"         # 평가하면 "hello"
len([1, 2, 3])  # 평가하면 3
x > 5           # 평가하면 True/False

이런 것들은 모두 그 자리에 결국 값이 남는다. 컴퓨터가 expression을 보면 "이걸 계산해서 값으로 바꿔라"라고 이해한다.

Statement: 프로그램 상태를 변화시키는 것

x = 5            # 효과: 변수 x에 5를 바인딩
print("hi")      # 효과: 화면에 출력
if cond: ...     # 효과: 조건에 따라 분기
return 42        # 효과: 함수에서 빠져나감

이건 그 자체가 값으로 환원되지 않는다. 대신 프로그램의 상태(메모리, 출력, 제어 흐름)를 바꾸는 행위다.

이론적 배경: 두 가지 계산 모델

이 둘의 분기는 사실 *"계산이란 무엇인가"*를 보는 두 가지 모델에서 나왔다.

1. 수학적 모델 — expression의 뿌리

수학에서 함수는 입력을 받아 출력을 낸다. 부작용이 없다. f(3) = 9는 그냥 9다. 어디다 대입하지도, 무엇을 바꾸지도 않고 그냥 값이다.

이걸 모델로 만든 게 **람다 계산법(λ-calculus)**이고, 거기서 모든 계산은 expression의 변환이다. (λx. x+1) 55+16. 중간에 "대입"이나 "상태 변화"라는 개념이 없다. 그냥 식이 다른 식으로 변할 뿐이다. Lisp, Haskell, ML은 이 전통을 따른다.

2. 기계적 모델 — statement의 뿌리

폰 노이만 컴퓨터는 메모리에 명령을 순서대로 실행한다.

1. 메모리 100번지에 5를 저장하라
2. 메모리 104번지에 10을 저장하라
3. 두 값을 더해서 108번지에 저장하라

각 명령은 상태(메모리)를 바꾸는 동작이지, 값을 만드는 게 아니다. C, Pascal, FORTRAN 같은 명령형 언어는 이 모델을 추상화한 것이다. 그래서 statement가 중심이 됐다.

두 모델의 만남

대부분의 현대 언어는 두 모델을 섞어 쓴다.

  • 수학적 부분 (expression): 3 + 4, x * y, len(arr)
  • 기계적 부분 (statement): x = 5, print(...), if/while

언어 설계자가 결정해야 하는 건 이 둘의 비율을 어떻게 둘 것인가이다. 그것이 what-is-expression-based-language|expression-based vs statement-based 스펙트럼의 정체다.

왜 statement가 따로 필요한가

수학적 관점에서만 보면 statement는 불순한 것이다. 수학 함수는 부작용이 없는데, statement는 부작용 그 자체이기 때문이다.

그러나 컴퓨터 프로그램은 결국 뭔가를 해야 한다.

  • 화면에 글자를 띄우거나
  • 파일을 저장하거나
  • 네트워크로 데이터를 보내거나

이건 다 *"세상의 상태를 바꾸는 행위"*다. 즉 부작용이다. 그래서 어떤 형태로든 statement(혹은 그에 준하는 무언가)가 필요하다.

순수 함수형 언어인 Haskell조차 이 문제를 피할 수 없어, 부작용을 monad라는 expression으로 포장해서 다룬다. *"표면적으로는 모든 게 expression이지만, 그 expression이 실행되면 부작용이 일어난다"*는 식의 우회로를 만든 것이다. 이는 "부작용 없이는 프로그램이 쓸모없다"는 현실과 "수학적 깔끔함을 유지하고 싶다"는 이상 사이의 타협이다.

"x = 5"는 statement인가 expression인가

언어마다 다르다. 이게 진짜 핵심이다.

// C: assignment는 expression. 값은 대입된 값.
int x;
int y = (x = 5);  // y == 5. 동작함.
if ((x = read()) > 0) { ... }  // 자주 쓰는 관용구
# Python: assignment는 statement (3.8 이전). 값 없음.
y = (x = 5)  # SyntaxError

# Python 3.8+에서 walrus operator(:=)가 추가됨
if (x := read()) > 0: ...  # 이건 expression
// Rust: assignment는 expression이지만 값은 () (unit type)
let mut x = 0;
let y = (x = 5);  // 컴파일 됨. y의 타입은 ().
-- Haskell: 변수에 "대입"이라는 개념 자체가 없음.
-- let 바인딩만 있고, 그조차 immutable.
let x = 5 in x + 1  -- 이 전체가 expression. 값은 6.

같은 x = 5인데 언어마다 분류가 다르다. 이것이 언어 설계자의 선택이라는 뜻이다. *"값을 반환할 만한 의미가 있는가? 오용 위험은 없는가? 합성에 유리한가?"*를 따져서 결정한다.

"한 줄 = statement"라는 직관

C나 Python에서 statement는 값이 변수에 대입되는 것까지고, expression은 그 값에 해당하는 것

이 직관이 왜 그렇게 보이는지를 풀어보면:

y = x + 3
# 이 한 줄 전체가 assignment statement
# 그 안의 "x + 3" 부분이 expression

statement가 큰 단위이고, expression은 그 안에 들어가는 작은 단위로 보인다. C/Python에서는 *"한 줄 = 한 statement"*가 일반적인 패턴이라서 이런 그림이 자연스럽다.

다만 더 정확한 그림은 이렇다.

Statement (x = expr;)
   └─ 안에 Expression(x + 3)이 부품으로 들어가 있음
       └─ 안에 더 작은 Expression(x)와 Expression(3)이 들어가 있음

Statement는 컨테이너고, expression은 내용물이다. C/Python에서는 이 컨테이너가 외부에서 더 큰 것 안에 들어갈 일이 거의 없어서(statement는 합성이 잘 안 됨) *"한 줄 단위"*로 보일 뿐이다.

반면 expression-based 언어에서는 컨테이너 자체가 또 다른 컨테이너의 내용물이 될 수 있다.

let result = if cond {
    let x = compute();    // statement
    x * 2                 // expression (블록의 값)
} else {
    0
};

여기서 if { ... } else { ... } 전체가 expression이고, 그 안에 statement(statements-vs-expressions|let statement)와 expression이 섞여 있다. 그리고 그 if expression은 다시 더 큰 let result = ...이라는 statement의 부품이 된다.

언어별 비교

언어x = 5if/match블록 {...}함수 본문
Cexpression (값 = 5)statementstatement (값 없음)statement 묶음
Pythonstatementstatement(블록 문법 없음)statement 묶음
Rustexpression (값 = ())expressionexpression (마지막 식이 값)마지막 expression이 반환값
Haskell(mutable 변수 자체가 없음)expression(do 블록은 monadic expression)전체가 expression

같은 문법 요소라도 언어가 어떻게 분류하느냐에 따라 코드의 모양이 완전히 달라진다.


한 줄 요약

Expression은 평가되어 값을 만드는 것, statement는 실행되어 효과를 일으키는 것이다. C/Python에서는 두 개념이 큰 컨테이너(statement)와 부품(expression)으로 깔끔히 나뉘는 것처럼 보이지만, 사실 이 둘의 경계와 합성 가능성은 언어가 선택하는 것이며, 그 선택이 코드의 모양 자체를 결정한다.