반응형
LinkedIn 개발자로 성장하면서 남긴 발자취들을 확인하실 수 있습니다.
Github WWDC Student Challenge 및 Cherish, Tiramisul 등 개발한 앱들의 코드를 확인하실 수 있습니다.
개인 앱 : Cherish 내 마음을 들여다보는 시간, 체리시는 디자이너와 PM과 함께 진행 중인 1인 개발 프로젝트입니다.
10년 후, 20년 후 나는 어떤 스토리 텔러가 되어 있을지 궁금하다. 내가 만약에 아직 조금 더 탐구하고 싶은 게 있고, 궁금한 게 있다면, 그게 설사 지금 당장의 내 인생에 도움이 안 되는 것 같더라도 경험해보자. 그 경험들을 온전히 즐기며 내 것으로 만들고, 내 일에 녹여내고... 그러다보면 그 점들이 모여 나란 사람을 그려내는 선이 될 테니까.

Recent Posts
Recent Comments
Total
관리 메뉴

꿈꾸는리버리

모나드란? with Context / Functor 본문

오뚝이 개발자/swift

모나드란? with Context / Functor

rriver2 2024. 3. 21. 00:16
반응형

휴.. 다시 마음을 잡고 Swift 공부를 시작했다.

오랜만에 문서를 읽고 공부하니까 재밌다!! 생산적인 input이 들어오는 것 같아서... ㅎㅎ

저번에 멈췄던 부분에서 다시 시작하려고 보니까 모나드에서 하면 되더라구요?

그래서 시작하는 모나드 이야기... 함수형 프로그래밍을 이해하는데에 모나드가 필수적이라 하는데,, 나중에 이 연관성에 대해 논할 수 있는 머리가 되면 다시 이야기를 덧붙여보겠습니다!


 모나드 그게 뭐죠? 

모나드.. 그게 뭔지 모르겠어서 사전에 쳤는데 이것도 뭐라는 지 모르겠어서.. 그냥 냅다 읽....었다

 

 모나드의 조건 

일단 모나드의 조건은 다음과 같다.

🍀 모나드의 조건 🍀
1. 타입을 인자로 받는 타입 (특정 타입의 값을 포장)
2. 특정 타입의 값을 포장한 것을 반환하는 함수가 존재
3. 포장된 값을 변환하여 같은 형태로 포장하는 함수가 존재

 

개념은 그렇게 어려운 게 아닌데 말이 어렵다... 

원래 안개 안에 있을 때가 어려운 법!! 하나씩 명료하게 만들어보자 !

 

일단 그 전에 소개할 것이 있다..!

모나드를 공부하면서 Key가 되는 단어가 3개가 있는데, 그것은 바로,

 Context(컨텍스트) / Functor(함수객체) / Monad(모나드) 

그림과 같이.. 모나드를 알기 위해서 컨텍스트와 함수 객체의 개념을 먼저 알아야 한다.


 1️⃣ Context(컨텍스트) 

우선 컨텍스트에 대해 알아보도록 하자. 사전적 정의는 다음과 같다. 맥락!!

어떤 내용의 맥락 ..! 즉, 내용을 "둘러싸고" 있는 것 이라고 생각할 수 있겠죠?

이와 마찬가지로 컨텍스트도 콘텐츠를 감싸고 있는 것이라고 생각할 수 있다.

 

이를 설명하는데에 옵셔널 예시를 많이 든다. 근데 나의 경우는... 옵셔널로 이해가 잘 안 가서 배열도 함께 예시를 들었다. ( 내가 이해한 바로 작성했기 때문에 배열 부분은 틀릴 수도 있는데, 혹시 틀린 것을 확인하신다면 댓글로 참교육 부탁드립니다.)

 

1) 옵셔널

옵셔널은 다음과 같이 .none case와 .some(value) case로 나뉜다.

즉, 옵셔널을 추출한다는 것은 열거형 인스턴스 내부의 .some(value) case의 연관 값을 꺼내오는 것과 같다.

 

그림으로 나타내면 아래와 같이 두가지 case로 나뉘게 되고, optional(2)에서 콘텐츠를 추출하면 2가 된다.

여기서 Optional이라는 게 컨텍스트로 생각하면 될 것 같다.

2) 배열

사실 어딜 뒤져봐도 Optional 이야기 밖에 없지만... 나는 개인적으로 옵셔널에 깊히 공감이 안 가서...

Array도 같은 맥락으로 생각해봤는데, 다음과 같은 그림이 그려지지 않을까 싶다.

 

 2️⃣ Functor(함수객체) 

함수객체란 .map을 적용할 수 있는 컨테이너 타입이다. 즉, Array, Dictionary, Set 이 컬렉션 타입은 모두 함수객체이다.

 

사실 map이라고 표현을 해서 의미가 다가오긴 했지만, 좀 더 확실하게 이야기를 하자면 컨텍스트와 콘텐츠를 가지는 녀석들을 함수객체라고 한다. '추출할 수 있다'라는 표현을 사용할 수 있다면 함수객체라고 생각해도 무방할 것 같다.

 

1) 옵셔널

아래의 코드를 보자. value의 경우 Int?형인데, map을 통해 value의 컨텍스트 안에 있는 콘텐츠, 즉 optional(2)에서 2를 추출해서 $0에 넣어준다. 그래서 $0이 Int형인 것이다.

 

옵셔널의 map 매서드가 구현된 부분을 보면 다음과 같다.

 

2) 배열

아래의 코드를 보자. list의 경우 [Int]형인데, map을 통해 value의 컨텍스트 안에 있는 콘텐츠, 즉 [1,2,3]에서 각각 1,2,3을 추출해서 $0에 넣어준다. 그래서 $0이 Int형인 것이다.

배열의 map 매서드가 구현된 부분을 보면 다음과 같다.

 3️⃣ Monad(모나드) 

이쯤에서.. 위에서 봤던 모나드의 조건을 한번 살펴보자.

🍀 모나드의 조건 🍀
1. 타입을 인자로 받는 타입 (특정 타입의 값을 포장)
2. 특정 타입의 값을 포장한 것을 반환하는 함수가 존재
3. 포장된 값을 변환하여 같은 형태로 포장하는 함수가 존재

 

1. 타입을 인자로 받는 타입 ( 특정 타입의 값을 포장 )

-> 일반적으로 제네릭하게 구현되어 특정 타입을 입력받아 다른 타입을 생성하는 것을 의미한다고 보면 된다. 언젠가 블로그에 제네릭에 대한 이야기도 하게 되겠지만,, 제네릭은 array나 optional에서 사용되는 게 Int로 국한되는 것이 아니라, String, Double 등등 다양하게 쓰일 수 있도록 하는 것이라고 쉽게 생각하면 된다.

즉, Array <????> 에서 ??? 자리 자체가 "타입을 인자"로 받는 것!

 

2. 특정 타입의 값을 포장한 것을 반환하는 함수가 존재

(특정 타입의 값을 포장한 것) == (콘텐츠를 컨텍스트에 포장한 것

아래와 같이 Optional의 경우에는 Int형 42를 넣어서 컨텍스트를 만들었고, Array의 경우에는 1,2,3을 넣어서 [1,2,3]이라는 컨텍스트를 만들었기 때문에 이에 해당

let wrappedInt: Int? = Optional.some(42)

let wrappedInts = Array<Int>(arrayLiteral: 1, 2, 3)

 

3. 포장된 값을 변환하여 같은 형태로 포장하는 함수가 존재

이 부분이 모나드의 정의인 "자신의 컨텍스트와 같은 컨텍스트의 형태로 맵핑할 수 있는 함수객체"라는 말과 연관이 된다. "자신의 컨텍스트와 같은 컨텍스트의 형태로 맵핑" 할 수 있는 고차함수는 flatmap이기에 많은 글들에서 "flatMap을 적용할 수 있는 것은 Monad이다." 라고 말한다.

 

아래의 코드를 보자. optionalString2의 경우 String?의 타입을 가진다. 즉 자신의 컨텍스트는 Optional<T>이다. 하지만, map을 하고 나면 Optional<Optional<T>>의 값이 된다. 

 

반면, flatMap을 사용하면 아래와 같이 optionalString의 경우 자신의 컨텍스트는 Optional<String>이다. 그리고 flatMap을 하고 나서도 Optional<T>의 값을 가지게 된다.

 

 

 🌟 map 과 flatMap의 차이 

let possibleNumbers = ["1", "2", "three", "///4///", "5"]

let mapNumbers = possibleNumbers.map { str in Int(str) } // [Optional(1), Optional(2), nil, nil, Optional(5)]
print("mapNumbers", mapNumbers)

let flatMapNumbers = possibleNumbers.flatMap { str in Int(str) } // [1, 2, 5]                                  
print("flatMapNumbers", flatMapNumbers)

위 코드를 보면 flatmap의 경우 map과 달리 optional을 벗겨서 나타내어준다. 즉, flatmap은 컨텍스트 내부의 컨텍스트를 모두 같은 위상으로 Flat(평평) 하게 펼처준다. 

let numbers = [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

let mapped = numbers.map { $0 } // [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
print(mapped)

let flatMapped = numbers.flatMap { $0 } // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(flatMapped)

위 배열의 코드를 보아도 알겠지만, 코드를 보면 2차원 배열을 flat하게 1차원 배열로 바꿔준 것을 알 수 있다.


 🌟 간단한 정리..

1. map을 적용할 수 있는 것은 functor이다.
2. flatMap을 적용할 수 있는 것은 Monad이다.
반응형
Comments