모나드라는 개념을 찾아보며 이해한 부분만 요약하여 정리한 글이라 내용이 다소 생략되어있습니다.
설명
하스켈 등 함수형 프로그래밍 언어에서 입출력 및 데이터 구조를 다룰 때 쓰인다.
“위키백과 - Monad - version of Korean”
뭔가 부족해서, 영어 버전으로 찾아봤습니다.
함수형 프로그래밍에서 모나드는 일반적으로 프로그램을 구조화할 수 있는 추상적 개념이다. 모나드는 두 가지 절차를 따라서 특정한 계산 형태를 나타내는 자체 데이터 type을 제공하여 달성한다.
- 모나드 내에서 기본 유형의 값을 래핑하는 것 (모나드 값 생성)
- 모나딕 값을 출력하는 함수를 구성하는 것 (모나딕 함수라고 함)
이를 통해 모나드는 광범위한 문제를 단순화할 수 있다.
“위키백과 - Monad - version of English”
- 모나드 패턴을 사용하기 위한 절차를 따르면 순수 함수형 프로그래밍의 이점을 많이 얻을 수 있다고 합니다.
- 모나드의 구조가
데코레이터
패턴과 유사하다고 생각이 들었습니다.
예시 코드 1
아래의 코드는 입력값 x에 1,2,3을 더한 값을 반환하는 코드입니다.
def f1(x): return x + 1
def f2(x): return x + 2
def f3(x): return x + 3
위의 코드를 이용해 x, x+1, x+1+2, x+1+2+3의 값을 구하는 코드를 작성해보면 아래와 같이 할 수 있습니다.
_list = [1] # x가 1인 경우로 가정
temp = f1(_list[0])
_list.append(temp)
temp = f2(temp)
_list.append(temp)
temp = f3(temp)
_list.append(temp)
print((temp, _list))
---------------------------------------------------
(7, [1, 2, 4, 7])
하지만 위 방식의 단점은 새로운 함수 f4, f5가 추가될 때 마다 리스트에 값을 수동으로 추가해야 하는 문제가 있습니다.
여기서 모나드 패턴을 사용해보면
def unit(x):
return (x, [x])
def bind(_list, f):
res = f(_list[0])
return (res, _list[1] + [res])
# x가 1인 경우로 가정
result = bind(bind(bind(unit(1), f1), f2), f3)
print(result)
---------------------------------------------------
(7, [1, 2, 4, 7])
재귀 함수의 느낌과 비슷했습니다.
예시 코드 2
이번엔 객체지향의 특징을 살려서 접근해봤습니다.
class Employee:
def get_boss(self):
# Return the employess's boss
def get_wage(self):
# Compute the wage
# janos라는 직원 객체 생성
janos = Employee()
# janos라는 직원의 보스를 얻고, 보스의 임금을 얻기
janos.get_boss().get_wage()
위의 경우에 get_boss()의 반환값이 None인 경우 get_wage()는 Error가 발생합니다.
이런 Error는 아래와 같은 방법으로 방지할 수 있습니다.
result = None
if not janos:
boss = janos.get_boss()
if not boss:
wage = boss.get_wage()
if not wage:
result = wage
print(result)
하지만 위의 코드는 파이썬 철학 Beautiful is better than ugly (아름다운 것이 추한 것보다 낫다)
를 지키지 못한 추한 코드라고 할 수 있습니다.
이 코드를 아름답게 만들기 위해 다시 한 번 모나드 패턴을 사용하면 아래와 같은 호출 형태로 result를 구할 수 있습니다.
def unit(emp):
return emp
def bind(emp, f):
return None if not emp else f(emp)
janos = Employee()
result = bind(bind(unit(john), Employee.get_boss), Employee.get_wage)
위의 코드를 아래의 함수를 추가해서 한 번 더 아름답게 만들 수 있습니다.
def pipeline(emp, *fs):
for f in fs:
emp = bind(emp, f)
return emp
전체적인 코드를 보면
def f1(x): return x + 1
def f2(x): return x + 2
def f3(x): return x + 3
def unit(emp):
return emp
def bind(emp, f):
return None if not emp else f(emp)
def pipeline(emp, *fs):
for f in fs:
emp = bind(emp, f)
return emp
janos = Employee()
result = pipeline(unit(x), f1, f2, f3)
pipeline 함수를 추가하면서 result를 구하는 bind(bind(...
부분을 단순한 형태로 대체할 수 있습니다.
pipeline 함수의 형태를 사용하면 이후에 추가될 수 있는 f4, f5, f6와 같은 추가 함수도 쉽게 활용할 수 있습니다.
result = pipeline(unit(x), f1, f2, f3, f4, f5, f6)
결론
모나드는 함수 구성을 위한 간단하고 강력한 디자인 패턴이다.
이해에 큰 도움이 되었던 페이지 링크를 첨부한다.
Reference - https://nikgrozev.com/2013/12/10/monads-in-15-minutes/