모나드라는 개념을 찾아보며 이해한 부분만 요약하여 정리한 글이라 내용이 다소 생략되어있습니다.

설명

하스켈 등 함수형 프로그래밍 언어에서 입출력 및 데이터 구조를 다룰 때 쓰인다.

“위키백과 - 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/