1. 순수 함수와 일급 객체
함수형 프로그래밍을 제대로 다루려면 어떤 변수를 함부로 건드려서는 안 되며, 함수 자체를 마음대로 굴릴 수 있어야 합니다. 그 가장 기본이 되는 개념들을 소개합니다.
1.1 오염되지 않은 기계: 순수 함수 (Pure Function)
‘순수 함수’란 동일한 입력이 들어가면 언제나 동일한 출력만 반환하며, 실행 도중에 외부에 있는 다른 변수를 몰래 수정하거나 파일에 쓰는 등 외부 세상에 어떤 흔적(부작용, Side-effect)도 남기지 않는 깔끔한 함수를 말합니다.
# [미순수 함수] 외부에 있는 리스트를 몰래 건드려서 상태를 변경함
bad_list = []
def bad_func(item):
bad_list.append(item) # 외부에 똥을 남김! (부작용 발생)
# [순수 함수] 외부 상태에 간섭하지 않고 오로지 새 값을 생성해 반환함
def pure_func(item, old_list):
# 기존 리스트를 복사한 새 리스트를 만듦 (원본은 안전!)
new_list = old_list[:]
new_list.append(item)
return new_list
함수형 프로그래밍에서는 이런 순수 함수들만 연결해서 프로그래밍하기 때문에 “이 변수 값이 도대체 어디서 바뀐거야?!” 라며 밤을 새우는 일이 확연히 줄어듭니다.
1.2 함수는 특별 대우를 받는 ‘일급 객체 (First-class Object)’
파이썬에서 함수는 코드 쪼가리가 아니라 어엿한 🌟일급 시민(First-class citizen)🌟 대우를 받는 객체입니다. 이름은 거창하지만, 결국 숫자나 문자열처럼 변수에 담거나, 다른 함수의 인자로 던져주거나, 함수의 결과값으로 리턴할 수 있다는 뜻입니다.
def say_hello():
return "안녕하세요!"
# 함수를 변수에 할당할 수 있습니다. (괄호 빼고 이름만!)
my_greet = say_hello
# 할당된 변수에 괄호를 치면 원래 함수가 실행됩니다.
print(my_greet()) # "안녕하세요!"
1.3 내 안의 나를 부른다: 재귀 호출 (Recursive Call)
함수형 코딩 스타일에서는 for, while 같은 반복문(상태가 변하는 변수가 필요하므로)의 사용을 자제합니다. 대신 함수 안에서 자기 자신을 다시 호출하여 반복을 해결합니다. 흔히 “재귀 호출”이라고 부릅니다.
# 리스트 안의 모든 숫자를 더하는 재귀 함수
def sum_all(numbers):
# 1. 끝나는 조건: 리스트에 숫자가 1개 남으면 그것을 던져준다.
if len(numbers) == 1:
return numbers[0]
# 2. 첫 번째 숫자(head)와 나머지(tail)로 쪼갠다.
head, *tail = numbers
# 3. 나머지를 처리하기 위해 나 자신을 다시 부른다!
return head + sum_all(tail)
print(sum_all([10, 20, 30, 40])) # 100
1.4 함수를 삼키는 거대한 입: 고차 함수 (Higher-order Function)
일급 시민인 함수를 인자로 받거나, 새로운 함수를 리턴하는 함수들을 거창하게 부르는 말이 바로 고차 함수입니다. 데코레이터에서 봤던 패턴이 모두 고차 함수에 해당합니다.
# 고차 함수: 첫 번째 인자로 '기능 부품(함수)'을 받습니다.
def do_math(func, a, b):
# 전달받은 함수 부품을 조립해 실행합니다.
return func(a, b)
def plus(x, y): return x + y
def multiply(x, y): return x * y
# 더하기 기계를 인자로 던짐
print(do_math(plus, 5, 2)) # 7
# 곱하기 기계를 인자로 던짐
print(do_math(multiply, 5, 2)) # 10
이러한 고차 함수 원리를 이용하면 틀은 그대로 둔 채 핵심 부품만 살짝 갈아 끼우는 레고 조립 같은 코딩이 가능해집니다.
서브목차