그래서 Monad를 왜 사용하는가?
lift2d
에 대해 배웠다.lift2d: ((T, U) -> V) -> (F<T>, F<U>) -> F<F<V>>
list
같은 것은 유용할 수 있지만, optional
같은 것은 별 쓸데가 없다.flat
을 쓰면 다중 제네릭을 제네릭 하나로 줄일 수 있다.T -> M<U>, U -> M<V>
두개의 함수가 있다고 하자.U
와 M<U>
는 다른 타입이기 때문이다.T
의 의미를 유지한 상태로 확장하는 타입이니까.// Optional
func f(_ t: T) -> Optional<U> {
// ...
}
func g(_ u: U) -> Optional<V> {
// ...
}
func gf(_ t: T) -> Optional<V> {
let opu = f(t)
switch opu {
case .none:
return .none
case .some(let u):
return g(u)
}
}
gf
와 같이 구현할 수 있다.swift
와 같은 언어에서는 이렇게 처리할 수 있겠다.lift(g): Optional<U> -> Optional<Optional<V>>
flat o lift(g): Optional<U> -> Optional<V>
lift(g)
를 적용하면 결과값이 Optional<Optional<V>>
가 나온다.flat
을 가지고 있으니 이러한 식으로 확장한 연산을 정의할 수 있게 된다.flatMap
이다.func f(_ t: T) -> Optional<U> {
// ...
}
func g(_ u: U) -> Optional<V> {
// ...
}
let result = Optional(3)
.flatMap(f) // Optional<T> -> Optional<U>의 함수로 확장
.flatMap(g) // Optional<U> -> Optional<V>의 함수로 확장
lift
의 변환함수로 Optional
이 리턴되는 함수를 넣었으나, 결과는 flat해서 나왔다.flatMap
이다.flat o lift
, 두 함수를 합성한 것을 기본 제공하는 것이 flatMap
이다.f: T -> M<U>
g: U -> M<V>
lift(g): M<U> -> M<M<V>>
flat o (lift(g)): M<U> -> M<V> // 어떠한 방식으로 합성하면~
(flat o (lift(g))) o f: T -> M<V> // 어떠한 방식으로 합성하면~
결론: flatLift(f) = flat o (lift(f))
gf
함수를 직접 만들지 않고,func flat<T>(_ value: Optional<Optional<T>>) -> Optional<T> {
switch value {
case .none:
return .none
case .some(let wrapped):
return wrapped
}
}
func lift<T, U>(_ transform: @escaping (T) -> U) -> (Optional<T>) -> Optional<U> {
return { (input: Optional<T>) -> Optional<U> in
switch input {
case .none:
return .none
case .some(let wrapped):
return .some(transform(wrapped))
}
}
}
func flatLift<T, U>(_ transform: @escaping (T) -> Optional<U>) -> ((Optional<T>) -> Optional<U>) {
return { (input: Optional<T>) -> Optional<U> in
flat(
lift(transform) // Optional<T> -> Optional<Optional<U>>
(input) // Optional<Optional<U>>
) // Optional<U>
}
}
// 다시 쓰면,
func flatLift<T, U>(_ transform: @escaping (T) -> Optional<U>) -> ((Optional<T>) -> Optional<U>) {
{ input in
flat(lift(transform)(input))
}
}
internal enum Optional<T> {
case none
case 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
}
}
internal func lift<T, U>(_ transform: @escaping (T) -> U) -> (Optional<T>) -> Optional<U> {
{ input in
switch input {
case .none:
return .none
case .some(let wrapped):
return .some(transform(wrapped))
}
}
}
internal func flatLift<T, U>(_ transform: @escaping (T) -> Optional<U>) -> ((Optional<T>) -> Optional<U>) {
{ input in
flat(lift(transform)(input))
}
}
}
lift
처럼 flatLift
은 다변수함수에 적용할 수 있는가?flatLift
가 lift
에서보다 다변수 함수에서 보다 더 유용하다.flat
할 수 있기 때문이다.flatLift2d: ((T, U) -> M<V>) -> ((M<T>, M<U>) -> M<V>)
lift
의 경우 차원 확장시, 변수 개수에 따라 반환 타입의 제네릭이 중첩되었지만,flatLift
의 경우 차원 확장시, 변수 개수에 따라 반환 타입의 제네릭이 중첩되지 않는다.do
(Haskell), for
(Scala)func lift<T, U>(_ transform: @escaping (T) -> U) -> (T?) -> U? {
{ input in
switch input {
case .none:
return .none
case .some(let wrapped):
return .some(transform(wrapped))
}
}
}
func flat<T>(_ value: T??) -> T? {
switch value {
case .none:
return .none
case .some(let wrapped):
return wrapped
}
}
func flatLift<T, U>(_ transform: @escaping (T) -> U?) -> (T?) -> U? {
{ input in
flat(lift(transform)(input))
}
}
func flatLift2d<T1, T2, U>(_ transform: @escaping (T1, T2) -> U?) -> (T1?, T2?) -> U? {
{ mt1, mt2 in
flat(lift { t1 in
flat(lift { 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)
}
}
func flatLift3d<T1, T2, T3, U>(_ transform: @escaping (T1, T2, T3) -> U?) -> (T1?, T2?, T3?) -> U? {
{ mt1, mt2, mt3 in
flatLift { t1 in
flatLift { t2 in
flatLift { t3 in
transform(t1, t2, t3)
}(mt3)
}(mt2)
}(mt1)
}
}
f: (T, U) -> Optional<V>
let opt: Optional<T>
let opu: Optional<U>
lift2d(f)(opt, opu)
flatLift2d(f)(opt, opu)
lift
를 만약에 걸었다면 위과 같은 결과가 나왔을 것이다.flatLift
를 사용하면 이렇게 된다.f: (T, U) -> [V]
let left = [t1, t2, t3]
let right = [u1, u2]
lift2d(f)(left, right)
flatLift2d(f)(left, right)
lift2d
를 걸면 이렇게 나온다.[
[f(t1, u1), f(t1, u2)],
[f(t2, u1), f(t2, u2)],
[f(t3, u1), f(t3, u2)]
]
flatLift2d
를 걸면 이렇게 나온다.[
f(t1, u1),
f(t1, u2),
f(t2, u1),
f(t2, u2),
f(t3, u1),
f(t3, u2)
]
flatLift: ((T1, T2...) -> M) -> ((M, M...) -> M)로 변환해주는 함수
((T1, T2...) -> U)
로 변환함수가 들어오면?unit
함수 걸어주면 된다.