그래서 Monad를 왜 사용하는가?
f1: T1 -> M<U1>
,f2: T2 -> M<U2>
,g: (U1, U2) -> M<V>
lift2d
를 g
에 사용해보자. (Monad는 Functor니까)func lift2d<T, U, V>(_ transform: @escaping (T, U) -> V) -> (F<T>, F<U>) -> F<F<V>> {
{ ft, fu in
lift { (t: T) -> V? in
lift { (u: U) -> V in
transform(t, u)
}(fu)
}(ft)
}
}
lift2d(g) // (M<U1>, M<U2>) -> M<M<V>>
f1
과 f2
를 합성해보자.let t1: T1 = someT1
let t2: T2 = someT2
lift2d(g)(f1(t1), f2(t2)) // M<M<V>>
M<M<V>>
가 나온다.flat
할 수 있어, 보통 원하는 결과인 M<V>
를 얻을 수 있다.func flatLift2d<T1, T2, U>(_ transform: @escaping (T1, T2) -> U?) -> (T1?, T2?) -> U? {
{ mt1, mt2 in
flatLift { t1 in
flatLift { t2 in
transform(t1, t2)
}(mt2)
}(mt1)
}
}
flatLift2d(g)(f1(t1), f2(t2)) // M<V>
Functor
로는 다변수 함수를 합성했을 때의 결과가 타입이 중첩되어 나온다.Monad
로는 이를 flat
할 수 있어 타입이 중첩되지 않는다.횡단 관심사(Cross-cutting concern)는 소프트웨어 개발에서 여러 부분에서 공통적으로 발생하는 기능 또는 관심사를 나타냅니다. 이러한 관심사들은 애플리케이션의 여러 모듈이나 컴포넌트에 걸쳐서 반복적으로 발생하며, 비즈니스 로직이나 주요 기능과는 독립적으로 존재합니다. 주요 기능과 별개로 존재하는 이러한 공통적인 관심사들은 코드의 중복, 가독성 저하, 유지보수 어려움 등을 야기할 수 있습니다.
flatLift
를 호출"해야 한다.func withdraw(_ money: Int?) -> Int? {
guard let money = money else {
self.error()
return nil
}
return money
}
func deposit(_ money: Int?) -> Int? {
guard let money = money else {
self.error()
return nil
}
return money
}
func error() -> String {
return "error"
}
let money = Optional(1000)
withdraw(money) // Optional(1000)
money
가 값이 없을 때 처리를 함수 내에서 처리해야 한다.withdraw
, deposit
모두 같은 동작을 처리해야 한다면, 이를 함수로 만들고 일일히 호출해줘야 한다.func withdraw(_ money: Int) -> Int? {
return money
}
func deposit(_ money: Int) -> Int? {
return money
}
let result = Optional(1000).flatMap(withdraw) // Optional(1000)
switch result {
case .none:
error()
case .some(let money):
// some action
}
func flatLifte1<T1, T2, U>(_ transform: @escaping (T1, T2) -> U) -> (T1?, T2) -> U? {
{ mt1, t2 in
flatLift { t1 in
transform(t1, t2)
}(mt1)
}
}
T1? -> U?
로 바뀜func flatLifte2<T1, T2, U>(_ transform: @escaping (T1, T2) -> U) -> (T1, T2?) -> U? {
{ t1, mt2 in
flatLift { t2 in
transform(t1, t2)
}(mt2)
}
}
func flatLifte3<T1, T2, U>(_ transform: @escaping (T1?, T2) -> U) -> (T1?, T2?) -> U? {
{ mt1, mt2 in
flatLift { t1 in
flatLift { t2 in
transform(t1, t2)
}(mt2)
}(mt1)
}
}
func flatLifte4<T1, T2, U>(_ transform: @escaping (T1, T2?) -> U) -> (T1?, T2?) -> U? {
{ mt1, mt2 in
flatLift { t1 in
flatLift { t2 in
transform(t1, t2)
}(mt2)
}(mt1)
}
}
func flatLift2d<T1, T2, U>(_ transform: @escaping (T1, T2) -> U?) -> (T1?, T2?) -> U? {
{ mt1, mt2 in
flatLift { t1 in
flatLift { t2 in
transform(t1, t2)
}(mt2)
}(mt1)
}
}
flatLift
연산의 경우 비보존력과 같은 특징을 갖는다 이해하면 되겠다.