오늘은 함수형 사고를 보는 대표적인 면접 문제를 정리해보겠습니다. 처음 보면 낯설지만, 핵심 원리만 이해하면 클로저함수를 값처럼 다루는 방식이 왜 중요한지 한 번에 감이 옵니다.


문제

cons(a, b)는 두 값을 하나의 쌍처럼 묶어 주는 함수입니다. 그리고 다음 두 함수가 있다고 가정합니다.

car(pair) — 쌍의 첫 번째 값을 반환

cdr(pair) — 쌍의 두 번째 값을 반환

예를 들면 다음과 같습니다.

car(cons(3, 4))  # 3
cdr(cons(3, 4))  # 4

다음과 같이 cons가 구현되어 있을 때, car와 cdr를 구현하는 것이 문제입니다.

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

처음 보면 왜 헷갈릴까?

보통 pair라고 하면 (a, b) 같은 자료구조를 떠올립니다. 그런데 여기서는 값을 직접 저장해서 꺼내는 방식이 아닙니다.

핵심

cons(a, b)는 실제로 pair 데이터를 반환하는 것이 아니라, 나중에 어떤 함수를 받으면 그 함수에 (a, b)를 넣어 실행해주는 함수를 반환합니다.

즉, cons(3, 4)의 결과는 이런 느낌입니다.

"함수 하나를 주면, 내가 그 함수에 3과 4를 넣어서 실행해줄게"


풀이 아이디어

car는 pair에게 첫 번째 값만 반환하는 함수를 넘기면 됩니다.

def first(a, b):
    return a

cdr는 pair에게 두 번째 값만 반환하는 함수를 넘기면 됩니다.

def second(a, b):
    return b

정답 코드

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

def car(pair):
    def first(a, b):
        return a
    return pair(first)

def cdr(pair):
    def second(a, b):
        return b
    return pair(second)

더 짧게 쓰는 방법

def cons(a, b):
    return lambda f: f(a, b)

def car(pair):
    return pair(lambda a, b: a)

def cdr(pair):
    return pair(lambda a, b: b)

실행 예시

p = cons(3, 4)

print(car(p))  # 3
print(cdr(p))  # 4

왜 이런 방식이 가능한가?

1. 클로저

cons(a, b)가 끝난 뒤에도 내부 함수 pair는 바깥 변수였던 a와 b를 기억합니다.

2. 함수도 값처럼 다룰 수 있다

파이썬에서는 함수를 다른 함수의 인자로 넘길 수 있습니다. 이 문제는 그 성질을 아주 직접적으로 보여줍니다.

3. 데이터를 함수로 표현할 수 있다

pair를 리스트나 튜플 같은 자료구조로 저장하지 않고, 함수 자체로 표현했기 때문입니다.

일반적인 pair는 서랍 안에 값 2개를 넣어두는 방식입니다. 반면 이 문제의 pair는 값 2개를 기억하고 있다가, 요청하는 방식대로 꺼내주는 직원에 가깝습니다.


마무리

이 문제는 코드 길이는 짧지만, 사고 방식은 꽤 깊습니다. 단순히 정답만 외우기보다 왜 pair를 함수로 표현할 수 있는지를 이해하면, 클로저와 고차 함수 개념이 훨씬 잘 잡힙니다.

정리하면, cons(a, b)는 두 값을 저장한 자료구조를 반환하는 것이 아니라 두 값을 기억하는 함수를 반환합니다. 따라서 car와 cdr는 각각 원하는 값을 꺼내는 함수를 전달해서 구현할 수 있습니다.