[Swift] Ch16. 모나드

JJUDEV·2024년 4월 18일
1

Swift

목록 보기
14/21
post-thumbnail

모나드는 여러 영역에서 다양한 뜻을 가지기도 하고 한 문장으로 설명하기 참 어려운 개념입니다. 범주론에서의 모나드는 매우 추상적인 수학적 개념이지만, 그 개념은 프로그래밍과 같은 구체적인 분야에서 매우 유용한 추상화와 도구를 제공합니다. 함수형 프로그래밍에서의 모나드는 순서가 있는 연산을 처리할 때 자주 활용하는 디자인 패턴이기도 합니다. 사용하는 곳에 따라 수학의 범주론에서 말하는 모나드인지 특정 디자인 패턴을 따르는 모나드인지가 다릅니다.
프로그래밍에서 사용하는 모나드는 범주론의 모나드의 의미를 완벽히 구현하려고 하지 않기 때문에 범주론의 모나드 개념을 차용한 정도의 의미를 갖습니다. 그래서 프로그래밍에서의 모나드를 모나딕(Monadic)이라고 표현합니다.

  • 프로그래밍에서 모나드가 갖춰야 하는 조건
    : 타입을 인자로 받는 타입(특정 타입의 값을 포장)
    : 특정 타입의 값을 포장한 것을 반환하는 함수(메서드)가 존재
    : 포장된 값을 변환하여 같은 형태로 포장하는 함수(메서드)가 존재

16.1 컨텍스트

컨텍스트는 '콘텐츠를 담은 그 무엇'을 뜻하며 물컵에 물이 담겨있으면 물은 콘텐츠고 물컵은 컨텍스트라고 볼 수 있습니다.
옵셔널을 예로 들면 2라는 수자를 옵셔널로 둘러쌓을 때 컨텍스트 안에 2라는 콘텐츠가 들어가는 모양새입니다. 이때 '컨텍스트는 2라는 값을 가지고 있다'고 말할 수 있으며 만약 값이 없는 옵셔널 상태라면 '컨텍스트는 존재하지만 내부에 값이 없다'고 할 수 있습니다.

func addThree(_ num: Int) -> Int {
	return num + 3
}
// addThree(_:) 함수의 전달인자로 컨텍스트에 들어 있지 않은 순수 값인 2를 전달하면 정상적으로 함수를 실행할 수 있음
// addThree(_:) 함수는 매개변수로 일반 Int 타입의 값을 받기 때문
addThree(2) // 5 //  정상 실행
addThree(Optional(2) // 오류 발생

16.2 함수 객체

맵(map)은 컨테이너(컨테이너는 다른 타입의 값을 담을 수 있으므로 컨텍스트의 역할을 수행할 수 있습니다.)의 값을 변형시킬 수 있는 고차함수입니다. 그리고 옵셔널은 컨테이너와 값을 갖기 때문에 맵 함수를 사용할 수 있습니다.

Optional(2).map(addThree)	// Optional(5)
// 맵 메서드를 사용하여 옵셔널을 연산할 수 있는 addThree(_:) 함수

'함수객체(Functor)'란 맵을 적용할 수 있는 컨테이너 타입이라고 할 수 있습니다. Array, Dictionary, Set 등등 스위프트의 많은 컬렉션 타입은 함수객체입니다.

16.3 모나드

함수객체 중에서 자신의 컨텍스트와 같은 컨텍스트의 형태로 맵핑할 수 있는 함수객체를 닫힌 함수객체(Endofunctor)라고 합니다. 모나드(Monad)는 닫힌 함수객체입니다.
Swift의 flatMap 메서드는 모나드의 중요한 연산 중 하나로, 모나드 값을 변환하고 중첩된 모나드 구조를 평탄화하는 역할을 합니다.

flatMap은 두 가지 중요한 작업을 수행

  • 매핑(Mapping): 컨테이너(예: 옵셔널, 배열) 안에 있는 각각의 값에 대해 함수를 적용합니다. 이 과정은 map 함수와 유사하지만,
  • 평탄화(Flattening): flatMap은 중첩된 컨테이너 구조를 평탄화합니다. 예를 들어, Optional<Optional>이 Optional으로, [[Element]]이 [Element]로 평탄화됩니다.
// Optional과 flatMap
let stringNumber: String? = "5"
let mapped: Int? = stringNumber.map { Int($0) }  // 결과: Optional(Optional(5))
let flatMapped: Int? = stringNumber.flatMap { Int($0) }  // 결과: Optional(5)
// 배열과 flatMap 
let arrayOfArrays = [[1, 2, 3], [4, 5, 6]]
let flatMappedArray = arrayOfArrays.flatMap { $0.map { $0 * 2 } }
// 결과: [2, 4, 6, 8, 10, 12]

flatMap을 사용하면 중첩된 모나드 구조를 간단하게 평탄화하고, 값 변환을 수행할 수 있어 코드를 더 깔끔하게 작성할 수 있습니다. Swift에서 flatMap은 옵셔널과 배열을 포함해 다양한 타입에 구현되어 있으며, 함수형 프로그래밍 패턴을 Swift 코드에 적용하는 데 유용한 도구입니다.

Swift에서 map, compactMap, flatMap의 차이와 예시를 들어보겠습니다.

  • map
    map 함수는 컬렉션의 각 요소에 대해 주어진 변환을 적용하고, 그 결과로 새 컬렉션을 생성합니다. map은 원본 컬렉션의 요소 수와 동일한 수의 요소를 가진 새 컬렉션을 반환합니다.
let numbers = [1, 2, 3, 4]
let squaredNumbers = numbers.map { $0 * $0 }
// squaredNumbers = [1, 4, 9, 16]
  • compactMap
    compactMap은 map과 유사하게 각 요소에 대한 변환을 적용하지만, 변환 과정에서 nil 결과를 제거하고 옵셔널이 아닌 값만을 포함한 새 컬렉션을 생성합니다. 이는 옵셔널을 반환하는 변환을 안전하게 처리할 때 유용합니다.
let stringNumbers = ["1", "2", "three", "4"]
let mappedNumbers = stringNumbers.map { Int($0) }
// mappedNumbers = [Optional(1), Optional(2), nil, Optional(4)]
let compactMappedNumbers = stringNumbers.compactMap { Int($0) }
// compactMappedNumbers = [1, 2, 4]

flatMap의 기능은 아래 두가지 사용 사례가 있습니다.

  1. 컬렉션에서의 flatMap: 이는 중첩된 컬렉션에 대해 map을 적용한 후, 결과로 나온 컬렉션을 하나의 컬렉션으로 평탄화합니다.
let nestedNumbers = [[1, 2, 3], [4, 5, 6]]
let flatMappedNumbers = nestedNumbers.flatMap { $0 }
// flatMappedNumbers = [1, 2, 3, 4, 5, 6]
  1. 옵셔널에서의 flatMap: 옵셔널에 적용될 때, flatMap은 주어진 변환을 적용하고, 변환된 결과가 옵셔널일 때 중첩된 옵셔널을 평탄화합니다.
let optionalString: String? = "5"
let flatMappedNumber = optionalString.flatMap { Int($0) }
// flatMappedNumber = Optional(5)

요약하면, map은 컬렉션의 각 요소에 대해 변환을 적용하고, compactMap은 이 과정에서 nil을 제거합니다. flatMap은 컬렉션을 평탄화하거나 옵셔널의 중첩을 해결하는 데 사용됩니다.

참고자료

  • 스위프트 프로그래밍 3판(지은이: 야곰, 출판사: 한빛미디어(주))
  • ChatGPT 4
profile
4년차 앱개발자입니다.

0개의 댓글