순수 함수형 프로그래밍언어 Haskell에는 Either라는 자료구조가 있다. 둘 중 하나의 타입을 가질 수 있는 경우 사용하는 자료구조라 한다. 이 Either 자료구조에 영감을 얻어 태어난 것이 Swift의 Result라 한다. 이번 글에서는 Either와 Result의 개념을 알아보고, 실제 사용할 때, 어떠한 식으로 변형하여 사용할 수 있는지 알아보자. 어떻게 보면 난해할 수 있으니 마음을 단단히 먹고 읽어보자.
enum Either<L, R> {
case left(L)
case right(R)
}
var either: Either<String, Int> = .right(3)
switch either {
case .left(let l):
print(l)
case .right(let r):
print(r)
}
enum Result<T> {
case success(T)
case failure(Error)
}
struct Result<T> {
var success: T?
var failure: Error?
}
성공 혹은 실패여야 하나, property로 갖고 있기 때문에 둘다 값이 있거나, 없는 경우를 배제할 수 없음
Functor로 개선하기
enum Result<T> {
case success(T)
case faliure(Error)
func map<U>(_ transform: (T) throws -> U) rethrows -> Result<U> {
switch self {
case .success(let t):
return Result<U>.success(try transform(t))
case .faliure(let error):
return Result<U>.faliure(error)
}
}
}
Monad로 개선하기
enum Result<T> {
case success(T)
case failure(Error)
static func flatten<T>(_ result: Result<Result<T>>) -> Result<T> {
switch result {
case .success(let t): // 성공하면 T 타입(여기서 T타입은 Result<T>로 대응됨
return t
case .failure(let e): // 성공이 아닌 경우는 무조건 Error Type임!!
return Result<T>.failure(e)
}
}
func map<U>(_ transform: (T) throws -> U) rethrows -> Result<U> {
switch self {
case .success(let t):
return Result<U>.success(try transform(t))
case .failure(let error):
return Result<U>.failure(error)
}
}
func flatMap<U>(_ transform: (T) throws -> Result<U>) rethrows -> Result<U> {
switch self {
case .success:
let transformed = try self.map(transform) // Result<Result<U>>: map은 transform이 완전 타입으로 리턴된다고 가정하고 Box를 한번 더 씌움
return Result.flatten(transformed) // flatten 작업을 해줌
case .faliure(let e):
return Result<U>.failure(e)
}
}
}
Result의 활용
struct Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
init?(dict: [String: Any]) {
guard let age = dict["age"] as? Int,
let name = dict["name"] as? String else {
return nil
}
self.init(name: name, age: age)
}
}
enum MyError: Error {
case parseError
case initializeError
}
func convertor4(data: Result<Data>) -> Result<Person> {
let result = data.flatMap { (data: Data) -> Result<[String: Any]> in
guard let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
return Result.failure(MyError.parseError)
}
return Result.success(json)
}
.flatMap { (dict: [String: Any]) -> Result<Person> in
guard let result = Person(dict: dict) else {
return Result.failure(MyError.initializeError)
}
return Result.success(result)
}
return result
}
Custom transform function 추가하기
enum Result<T> {
// ...
func errorMap<U>(_ transform: (T) throws -> U) -> Result<U> {
do {
return try self.map(transform)
} catch let e {
return Result<U>.failure(e)
}
}
}
func convertor5(data: Result<Data>) -> Result<Person> {
let result = data.errorMap { try JSONSerialization.jsonObject(with: $0, options: []) }
.errorMap{ try ($0 as? [String: Any]).unwrap(errorIfNil: MyError.castingError) }
.errorMap{ Person(dict: dict) }
return result
}
func convertor6(data: Result<Data>) -> Result<Person> {
let result = data
.errorMap(jsonParser(data:))
.errorMap(checkType(any:))
.errorMap(Person.init(dict:))
}
효과