Monad는 어떻게 정의할 수 있을까?
Monad: 다음의 연산들이 정의된 Functor
unit:
T -> M<T>
(return
in Haskell)flat:
M<M<T>> -> M<T>
(join
in Haskell)
unit
하고 flat
추가한게 모나드다.unit
, flat
이 어떤 조건을 만족해야 하는지는 가장 아래에서 알아보자.unit
, flat
함수가 가져야할 추가 조건에 대해 설명한다.T->U
로 가는 함수를 f
라고 정의해보자.lift
함수에 f
를 인자로 넣어서 나오는 반환 값의 타입은 M<T> -> M<U>
이다.M<T>
를 인자로 넣어서 나오는 반환 값의 타입은 M<U>
이다.f
를 적용한 후 unit
함수를 적용한 것과unit
함수를 통해 M<T>
를 M<U>
로 바꾸고, f
를 적용한 것이 같다는 말이다.naturality for flat.
T->U
로 가는 함수를 f
라고 정의해보자.lift
함수에 f
를 인자로 넣어서 나오는 반환 값의 타입은 M<T> -> M<U>
이다.M<T>
를 위 함수에 넣으면 반환값은 M<U>
이다.M<M<T>>
를 M<M<U>>
로 바꾸려면 lift(lift(f))
를 적용해야 한다.lift(lift(f))
를 적용한 후 flat
함수를 적용한 것과flat
함수를 적용하고 lift(f)
를 적용한 것이 같다는 말이다.M<T>
를 M<M<T>>
로 바꾸는 방법은 두개가 있다.unit
을 적용하거나, lift(unit)
을 적용하거나.unit
이라는 함수의 이름이 이 때문인 듯 하다.lift
를 여러번 적용했을 때의 함수와 unit
을 적용했을 때의 함수가 같다.unit
을 적용한다는 것은 M<T>
타입 자체에 함수 동작을 걸어버리는 것이고,lift(unit)
을 적용한다는 것은 T
타입에 unit
함수를 적용한다는 가정을 한 상황에서 lift
를 통해 한차원 높인 상태의 함수를 만들고 적용한다고 생각할 수 있겠다.M<M<T>>
에 flat
함수를 걸었을 때 결과는 M<T>
로 나와야 한다는 것이 항등성이다.M<M<M<T>>>
를 M<M<T>>
로 바꾸는 방법은 두개가 있다.flat
을 적용하거나, lift(flat)
을 적용하거나.T->U
로 보내는 함수들의 집합을 하나 생각해보자.T
, U
는 제네릭으로 표현되었으니, 하나하나 구체 타입을 넣어보면 원소들이 있는 공간이 떠오를 것이다.M<T>->M<U>
로 보내는 함수들의 집합도 생각해보자.Double->String
으로 가는 함수 원소의 개수도 엄청많은데, 사실 그 변형 함수 로직의 다양성까지 포함해야한다.T->U
로 가는 함수 원소를 대표할 수 있게 위와 같이 그림을 그려보았다.f
와 같은 형태로 변수로 표현했다.lift(f)
이다.Double
자료형은 1.0, -3000과 같은 다양한 실수값을 반영할 수 있는 집합의 개념이다.f
라는 변환은 값들이 U
의 공간에 특정 점에 매핑된다고 할 수 있다.M<T>->M<U>
에 대응되는 원소는 어떻게 그릴 수 있을까?f
에 대응되는 녀석)은 무엇이 될 수 있을까?unit
함수의 의미부터 알아보자.unit
함수는 함수를 원소로 갖는 두 집합 사이의 관계를 정의한다.lift
은 어떻게 도식할 수 있을까.T
와 U
사이에 f
라는 논리적 관계가 정의되어 있다면, T
와 U
를 unit
한 값들 사이에서도 lift(f)
로 표현되는 논리적 관계가 존재해야 한다.unit
이라는 관계는 값들 사이의 논리적 관계를 전부 보존하는 변환이어야 한다.T
타입을 모나드화 하는 unit
연산의 수행 결과는, T
의 의미를 전부 보존해야만 한다.Int
을 Double
로 바꾸는 연산.M<T>
는 T
또는 T
와 논리적으로 동등한 개념을 지칭하는 타입이라는 것을 알 수 있다.flat
은 M<M<T>>
를 M<T>
로 바꿔주는 연산이다.lift(lift(f))
로 표현되었던 연산의 "일부" 논리적 관계는 보존해야 한다.flat
이라는 함수는 값의 의미를 적어도 일부는 보존한 채 M<M<T>>
를 M<T>
로 바꾸는 변환이 되어야 한다.M<T>
는 T
의 의미를 확장한 의미를 가진 타입이어야 한다. (unit
)M<M<T>>
는 어떤 의미에서는 M<T>
와 같이 간주될 수 있어야 한다. (flat
)T
의 의미를 확장한 Optional
의 의미는 T
또는 nil
이다. 즉, 포함한 채로 확장한 의미이다.Optional<Optional<T>>
는 Optional<T>
와 같이 간주될 수 있다.T
의 의미를 확장한 Array
의 의미는 T
의 집합이다. 즉, 포함한 채로 확장한 의미이다.Array<Array<T>>
는 Array<T>
와 같이 간주될 수 있다.모나드는 어떠한 개념에 대한 논리적 확장으로, 오직 한번만 의미있게 적용 가능한 것들을 통칭하는 개념이다.
M
이 Monad라면,
T
의 의미를 확장할 수 있는 방법이 정의되어 있어야 한다.flatMap
) 구현하는 경우가 많다.extension Optional {
internal func unit(_ t: T) -> T? {
.some(t)
}
}
extension Optional {
internal func flat<T>(_ oot: Optional<Optional<T>>) -> Optional<T> {
switch oot {
case .none:
return .none
case .some(let ot):
return ot
}
}
}
"자연성(naturality)"은 수학에서 함수나 변환자(transformer) 사이의 관계가 다른 수학적인 구조를 변형해도 변환의 특성을 유지하는 것을 의미합니다. 이러한 관계는 수학적인 구조 간에 일관성을 유지하며 변환을 적용할 수 있도록 해줍니다.
예를 들어, 두 개의 함수 F와 G가 있고, 어떤 구조에서 F와 G를 적용할 때 일관성이 유지된다고 가정해봅시다. 그렇다면 이 두 함수 사이의 관계는 "자연성"을 가진다고 할 수 있습니다. 즉, 어떤 변환을 적용하더라도 결과가 일관성 있게 유지되는 것입니다.
모나드(Monad)의 항등성(identity law)은 함수형 프로그래밍에서 모나드가 가져야 하는 중요한 특성 중 하나입니다. 모나드는 데이터 형식을 다루는 추상적인 개념으로, 값을 감싸거나 조작하는 데 사용됩니다. 이 때 모나드의 항등성은 모나드의 동작을 보다 안정적이고 일관적으로 만들어줍니다.
모나드의 항등성은 크게 두 가지 관점에서 설명될 수 있습니다
어떤 값을 모나드로 감싼 후에 해당 모나드를 특정 함수에 적용하는 것과, 그 값을 바로 그 함수에 적용하는 것은 같아야 합니다. 즉, m이라는 모나드와 함수 f가 있다면, 아래와 같은 관계가 성립해야 합니다
flatMap(unit(x), f) == f(x)
여기서 unit(x)는 값을 모나드로 감싸는 역할을 하는 함수입니다. flatMap은 모나드의 값을 함수에 적용하는 연산을 나타내며, f는 임의의 함수를 나타냅니다.
모나드에 값을 적용한 뒤에 모나드를 벗겨낸 결과와, 그 값을 바로 모나드에 감싸지 않은 결과는 같아야 합니다. 즉, m이라는 모나드와 함수 f가 있다면, 아래와 같은 관계가 성립해야 합니다:
flatMap(m, unit) == m
여기서 unit은 값을 모나드로 감싸는 역할을 하는 함수입니다.