[함수형 프로그래밍] Functor와 Monad

2020. 4. 9. 16:10iOS

Functor

  • map 함수를 지원하는 컨테이너 타입(map을 구현하는 타입)
  • Context + value + map transform = Functor
    • Context → 어떤 value 가 처해 있는 상태
      • Collection, Optional 등의 값을 가지는 container
      • 포함하는 value가 generic으로 표현 되어야 함
    • Value → Context에 넣어지는 실제 내용
      • Context가 generic으로 표현되기 때문에 어떤 타입의 value라도 사용 가능
    • Transform → 어떤 값 T를 U로 변환해 주는 function
      • T와 U는 같은 타입이여도 상관 없음
  • 컨테이너에서 값을 뺀 후, 값에 특정 함수를 적용해 타입과 값을 변경하고, 다시 값을 컨테이너에 넣는 것
  • Array 타입은 map 함수를 지원하기 때문에 Functor 이다.
  • let numbser: [Int] = [1, 2, 3] var doubledNumbers: [Int] = [] for number in numbers { doubledNumbers.append(number * 2) } print(doubledNumbers) // [2, 4, 6]

map

  • map을 사용하면 array의 요소를 변환하는 작업 의도를 더 명확하게 표현할 수 있다.
  • '어떻게'가 아닌 '무엇을 달성하려는지'를 더 잘 표현Optional
  • let numbers = [1, 2, 3] let doubledNumbers = numbers.map { $0 * 2 } print(doubledNumbers) // [2, 4, 6]
  • Optional도 map이 적용 가능한 컨테이너 타입
  • let number: Int? = 123 // == Optional(123) let transNumber = number.map { $0 * 2 } .map { $0 % 2 == 0 } print(transNumber) // Optional(true)
  • Optional.map은 nil을 대신 처리해준다.
  • 원래 값이 nil이라면 map을 적용해도 nil을 반환
  • let number: Int? = nil let transNumber = number.map { $0 * 2 } .map { $0 % 2 == 0 } print(transNumber) // nil

Result

  • 몇몇 프로그래밍 언어에서 Either라고 알려진 타입의 구현
  • case Error 연관 값은 제네릭 대신 NSError 타입으로 연산의 결과를 보고하는데 사용
  • 개념적으로 Result는 값이 있을 수도 있고, 없을 수도 있는 임의의 타입의 값을 포장하는 Optional과 유사
  • 왜 그 값이 없는지도 알려줌 → NSError
  • extension Result { func map<U>(f: T -> U) -> Result<U> { switch self { case let .Value(value): return Result<U>.Value(f(value)) case let .Error(error): return Result<U>.Error(error) } } }
  • 위의 예제는 Result.map을 구현한 코드
  • f는 타입 T의 값을 받고 타입 U를 반환한다
  • 값이 있으면 value를 파라미터로 f를 호출, 값이 없다면 error를 반환

Dictionary, Clourse 같은 타입도 Functor이라고 할 수 있다.

Monad

  • 포장된 값을 반환하는 함수에 포장된 값을 적용한다
  • 값이 있을 수도 있고 없을 수도 있는 상태를 포장하는 타입(Swift에서는 Optional)
  • Functor의 한 종류
  • flatMap 연산이 가능한 모든 것을을 모나드라고 볼 수 있다
// map
func mapEx<U>(_ transform: (Wrapped)->U) -> U?{
    switch self {
        case .some(let value):
            return transform(value)
        case .none:
            return .none
    }
}

// flatMap
func flatMapEx<U>(_ transform: (Wrapped) ->U?) -> U?{
    switch self {
        case .some(let value):
            return transform(value)
        case .none:
            return .none
    }
}
  • transform 메소드 반환 타입이 다르다
let stringNumber: String? = "5"  
let mapResult = stringNumber.mapEx {Int($0)} // Optional<Optional>  
let flatMapResult = stringNumber.flatMapEx {Int($0)} // Optional