함수형 프로그래밍을 공부하다보면 벽을 한번 마주한다. Functor와 Monad가 그것이다. 이걸 이해하기 위해서 위키피디아에서 집합론?, 범주론? 이런 걸 읽었던 적이 있는 것 같은데 여전히 잘 모르겠다. 이번에는 와닿는 방식으로 이해하는 것을 목표로 한다.
optional의 map
Collection의 map
Dictionary의 map
function의 map
map을 이용한 transform을 지원하는, value를 가지는 context
Context + value + transform(map) = Functor
Context에 싸여있는 Value에 function을 적용하기 위해 사용
enum Optional<Wrapped> {
case some(Wrapped)
case none
}
Optional(2).map { (v: Int) -> Int in
return v + 3
}
Chaining
let result = a?.b?.c?.d?.e
let result = a.map { $0 }.map { $0 }.map { $0 }.map { $0 }
Function에 map 함수를 사용할 수 있을까?
func map<A, B, C>(f: @escaping (B) -> C, g: @escaping: (A) -> B) -> (A) -> C {
return { x in
f(g(x))
}
}
Result Functor
enum Result<T> {
case value(T)
case error(Error)
func map<U>(_ transform: (T) -> U) -> Result<U> {
switch self {
case .value(let value):
let transformed = transform(value)
return Result<U>.value(transformed)
case .error(let error):
return Result<U>.error(error)
}
}
}
Optional은 값이 있거나 없거나
하지만 실패하는 경우를 알아보기 위해서 Optional만을 사용하는 것은 정보를 많이 제공하지 못한다.
그런데 case만 가지고 있는 자료구조인 경우, 실제 사용할 때, switch 문을 통해서 계속 판단해줘야 한다. 지저분 하다.
그래서 내부에 map 함수를 도입해서(리턴값을 보면 context가 유지되기 때문에 map이 맞다) 간결하게 해결하고자 함
활용
func f(string: String) -> Result<Int> {
guard let integer= Int(string) else {
return Result<Int>.error(MyError.notANumber)
}
return Result<Int>.value(integer)
}
func transform10(value: Int) -> Int {
return value * 10
}
let result = f(string: "1234").map(transform10(value:))
flatMap을 이용한 transform을 지원하는 value를 가지는 context
Context + value + flatMap transform = Monad
Optional(nil)
, Optional(Optional(3))
nil
, Optional(3)
을 원함Monad 활용
서버로 부터 받은 Data가 다음과 같다고 하자
{ "age": 3, "name": "kim" }
JSON Parser 후, Struct로 변환
struct Person {
var name: String
var age: Int
}
기존 코드
struct Person {
var name: String
var age: Int
}
func convertor(data: Data?) -> Person? {
guard let unwrappedData = data,
let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []),
let dict = json as? [String: AnyObject],
let name = dict["name"] as? String,
let age = dict["age"] as? Int else { return nil }
return Person(name: name, age: age)
}
변경된 코드
struct Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
init?(dict: [String: AnyObject]) {
guard let age = dict["age"] as? Int,
let name = dict["name"] as? String else {
return nil
}
self.init(name: name, age: age)
}
}
func parser(data: Data) -> [String: AnyObject]? {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
return (json as? [String: AnyObject])
}
func convertor2(data: Data?) -> Person? {
return data.flatMap({ parser(data: $0) })
.flatMap({ Person(dict: $0) })
}
func convertor3(data: Data?) -> Person? {
return data.flatMap(parser(data:))
.flatMap(Person.init(dict:))
}
결론