변수, 방정식, 그리고 순수함수

변수는 placeholder이고, 방정식은 문제 명세서이고, 함수는 입력→출력 매핑이다. 수학의 함수가 순수함수와 정확히 같다는 것을 확인한다.

변수, 방정식, 그리고 순수함수

Stage 1에서는 구체적인 수를 다뤘어. 3 + 5 = 8, 2³ = 8. 근데 여기서 한 단계 더 추상화할 수 있어.

"어떤 수에 5를 더하면 8이 된다. 그 수는?"

이걸 매번 말로 쓰면 귀찮으니까, 모르는 수를 기호로 놓는 거야: x + 5 = 8. 이게 대수(Algebra)의 시작이야.


변수: 수학 vs 프로그래밍

변수는 **"아직 모르는 값의 placeholder"**야. 프로그래밍에서 변수와 비슷하지만 결정적인 차이가 있어.

변수의 두 얼굴
Math
수학의 변수:
x + 5 = 8이면 x는 3일 수밖에 없음
값이 "변하는" 게 아니라
정해져 있는데 우리가 모를 뿐

→ "미지수" (unknown)
Code
// 프로그래밍의 변수:
let x = 3;
x = 5;  // 재할당 가능!
// 값을 담는 상자 — 바뀔 수 있음

// const가 수학의 변수에 더 가까움
const x = 3; // 한번 정해지면 안 변함
Connections
1미지수 (정해져 있음)=const (불변)
2매개변수 (함수에서)=함수 파라미터

수학의 변수는 "변하는 수"라기보다 "미지수" -- 값이 정해져 있는데 우리가 아직 모를 뿐이야. (나중에 함수에서는 진짜 변하는 의미로도 쓰이긴 해.)


방정식: 문제 명세서

방정식은 "이 조건을 만족하는 x를 찾아라"라는 스펙이야. x + 5 = 8은 "더해서 8이 되는 수를 찾아라"는 문제 명세서지.

방정식을 푼다는 건, 역연산을 연쇄적으로 적용해서 x를 혼자 남기는 과정이야:

x + 5 = 8
x + 5 - 5 = 8 - 5    ← 덧셈의 역연산(뺄셈)을 양변에
x = 3

Stage 1에서 배운 "모든 연산에는 역연산이 있다"가 여기서 바로 써먹히지.

좀 더 복잡한 것도 같은 원리야:

3x + 7 = 22
3x = 15        ← 뺄셈 (덧셈의 역)
x = 5          ← 나눗셈 (곱셈의 역)

양변에 같은 연산을 하면 등호가 유지된다 -- 이게 방정식 풀이의 유일한 규칙이야. 나머지는 전부 이 규칙에서 나와.

반복 테마: 연산 ↔ 역연산

방정식을 푸는 건 결국 역연산을 순서대로 적용하는 것이야. 이 패턴은 Stage 5의 가우스 소거법(행렬 방정식)까지 동일하게 이어져.


함수: 입력과 출력의 관계

함수의 본질

함수는 한 문장으로 정의할 수 있어:

"입력 하나에 출력 하나가 정해지는 관계"

그게 전부야. f(x) = 2x + 1이면 f(3) = 7, f(0) = 1, f(-2) = -3. 같은 입력을 넣으면 항상 같은 출력이 나와. 이게 함수의 핵심 조건이야.

수학 함수 = 순수함수

수학의 함수:
f(x) = x² + 1

- 부수효과(side effect) 없음
- 같은 입력 → 항상 같은 출력 (참조 투명성)
- "계산 과정"은 신경 안 씀. 입력-출력 매핑만 중요
- 시간 개념 없음

수학의 함수는 프로그래밍의 **순수함수(pure function)**에 정확히 대응해. 사실 함수형 프로그래밍이 수학의 함수 개념을 그대로 가져온 거야.

순수성 ≠ 멱등성 (중요한 구분)

흔한 혼동

"수학의 함수는 멱등성이 보장된다" -- 이건 틀려.

  • 순수성: 같은 입력 → 항상 같은 출력. 수학 함수는 항상 만족.
  • 멱등성: f(f(x)) = f(x). 한 번 적용 = 두 번 적용. 일부만 만족.

f(x) = x²는 순수함수이지만, f(f(3)) = f(9) = 81 ≠ 9. 순수하지만 멱등하지 않아.

정확한 명제: "수학의 함수는 순수성이 항상 보장되고, 프로그래밍에서는 순수함수만 이에 해당한다."


정의역, 공역, 치역 = 타입 시그니처

함수의 타입 시그니처
Math
f: ℝ → ℝ, f(x) = x²
정의역 = ℝ (입력 가능한 값)
공역 = ℝ (출력이 속하는 범위)
치역 = [0, ∞) (실제 출력 값)
Code
function square(x: number): number {
  return x * x;
}
// 입력 타입: number (정의역)
// 리턴 타입: number (공역)
// 실제 출력: 0 이상만 (치역)
Connections
1정의역 (domain)=입력 타입 (parameter type)
2공역 (codomain)=리턴 타입 (return type)
3치역 (range)=실제 출력 범위

정의역(domain)은 입력 타입, 공역(codomain)은 출력 타입이야. 그리고 실제로 출력되는 값들의 집합이 치역(range)이고.

f(x) = x²에서 입력이 모든 실수라도, 출력은 0 이상만 나오잖아. 공역은 ℝ 전체지만 치역은 [0, ∞)인 거지. 리턴 타입은 number지만 실제로는 음수가 절대 안 나오는 것과 같은 이치야.


함수 합성 = 파이프라인

함수를 연결할 수 있어:

f(x) = 2x      "두 배로"
g(x) = x + 1   "1 더하기"

g(f(3)) = g(6) = 7     ← f 먼저, 그 다음 g

프로그래밍의 파이프라인이랑 완전 같지:

javascript
const result = pipe(3, double, addOne);  // 3 → 6 → 7

그리고 합성 순서가 중요해:

g(f(3)) = g(6) = 7
f(g(3)) = f(4) = 8    ← 다른 결과!

교환법칙이 안 되는 거야. Article 1에서 "행렬곱은 교환법칙이 안 된다"고 살짝 언급했는데, 그 이유가 여기에 있어. 행렬은 함수이고, 행렬곱은 함수 합성이고, 함수 합성은 순서가 중요하니까.


일차함수: 변화율의 첫 만남

f(x) = ax + b

이게 전부야. 입력에 뭔가를 곱하고(a), 뭔가를 더한다(b). 그래프로 그리면 직선이야.

b는 시작점, a가 진짜 중요해

b는 쉬워 -- f(0) = b니까 y절편, 즉 **초기값(initial value)**이야.

a가 핵심 -- 기울기:

f(x) = 2x일 때:
x=0 → 0
x=1 → 2     (x가 1 늘 때 출력이 2 늘었음)
x=2 → 4     (x가 1 늘 때 출력이 2 늘었음)
x=3 → 6     (x가 1 늘 때 출력이 2 늘었음)

입력이 1 변할 때 출력이 얼마나 변하는가 -- 이게 기울기야. 일차함수에서는 이 변화율이 어디서든 일정해.

기울기 = (출력의 변화량) / (입력의 변화량) = Δy / Δx

기울기 = 변화의 속도. 이 직관이 Stage 4 미적분에서 미분으로 직결돼. 미분은 "기울기가 일정하지 않은 함수에서 순간 기울기를 구하는 것"이거든.

두 점이면 기울기를 구할 수 있다

점 (1, 3)과 (4, 9)를 지나는 직선의 기울기:
a = (9 - 3) / (4 - 1) = 6 / 3 = 2

현실에서는 함수의 식을 모르는 경우가 대부분이야. 데이터 포인트만 있지. 그때 두 점 사이의 기울기를 구하면 **"이 구간에서 평균적으로 얼마나 변했는가"**를 알 수 있어.

이 "두 점 사이의 평균 변화율"을 구간을 극한까지 좁히면 어떻게 될까? 그게 미분이야. 아직 안 가지만, 씨앗이 여기 심겨진 거야.