์์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ธ์ด Haskell์๋ Either๋ผ๋ ์๋ฃ๊ตฌ์กฐ๊ฐ ์๋ค. ๋ ์ค ํ๋์ ํ์ ์ ๊ฐ์ง ์ ์๋ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ ์๋ฃ๊ตฌ์กฐ๋ผ ํ๋ค. ์ด Either ์๋ฃ๊ตฌ์กฐ์ ์๊ฐ์ ์ป์ด ํ์ด๋ ๊ฒ์ด Swift์ Result๋ผ ํ๋ค. ์ด๋ฒ ๊ธ์์๋ Either์ Result์ ๊ฐ๋ ์ ์์๋ณด๊ณ , ์ค์ ์ฌ์ฉํ ๋, ์ด๋ ํ ์์ผ๋ก ๋ณํํ์ฌ ์ฌ์ฉํ ์ ์๋์ง ์์๋ณด์. ์ด๋ป๊ฒ ๋ณด๋ฉด ๋ํดํ ์ ์์ผ๋ ๋ง์์ ๋จ๋จํ ๋จน๊ณ ์ฝ์ด๋ณด์.
Maybe / Optional
- Value์ ๋ถ์ฌ ๊ฐ๋ฅ์ฑ์ ํํ
- functor์ด์ Nomad
- map, flatmap ์ฌ์ฉ๊ฐ๋ฅ
- value๊ฐ ๋ถ์ฌ ์ํ๋ก ๋ณํ ๊ฐ๋ฅ์ฑ์ด ์๋ transform (nil ๋ฆฌํด ์์)์ ๊ฒฝ์ฐ map ์ฌ์ฉ
- value๊ฐ ๋ถ์ฌ ์ํ๋ก ๋ณํ ๊ฐ๋ฅ์ฑ์ด ์๋ transform (nil ๋ฆฌํด)์ ๊ฒฝ์ฐ flatMap ์ฌ์ฉ
Either
- ์ด๋ค Value๊ฐ ๋๊ฐ์ง์ type์ ๊ฐ์ง ์ ์๋ ๊ฒฝ์ฐ ํํ
- value๊ฐ A or B
- ํต์ right/left๋ก ํํ
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)
}
- ์ ๋ฆฌ
- ํ๋์ ๋ณ์
- String์ ์ ์ฅํ๊ฑฐ๋ (left)
- Int๋ฅผ ์ ์ฅํ๊ฑฐ๋ (right)
- ํ๋์ ๋ณ์์ ๋ค๋ฅธ ๋๊ฐ์ง type์ ์ ์ํ์ฌ ์ฌ์ฉ
- ์ฌ์ฉ ๊ฒฝ์ฐ
- function์ด ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋๊ฐ์ง type์ return value๋ฅผ ๊ฐ์ง๋ ๊ฒฝ์ฐ
- ๋ํ์ ์ผ๋ก ์ ์์ ์ธ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ์ด๊ฑฐ๋ Error
- Optional๋ก ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์ด๋ค Error์ธ์ง ๋ช ์ํ ๋ฐฉ๋ฒ์ด ์์
- Swift์ Result๊ฐ ์ฌ์ฉ์
Result
- Either์์ ์ ์/Error๋ก ์ ํ์ ๊ฑธ์ด ์ฌ์ฉํ๋ ์๋ฃใ ๊ตฌ์กฐ
enum Result<T> {
case success(T)
case failure(Error)
}
- Struct๋ก ํ๋ค๋ฉด?
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) } } }
- rethrows
- ์๋ฌ๋ฅผ ๋ฆฌํดํ ์ ์๋ ํจ์๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ํจ์(์ด ๊ฒฝ์ฐ์์๋ map)๊ฐ ๋ค์ ํด๋น ์๋ฌ๋ฅผ ๋ฐ๊นฅ์ผ๋ก ๋ฑ๊ณ ์ถ์ ๋ ์ฌ์ฉ
- ์ฐธ๊ณ
- map์ transform ํจ์๋ return value๊ฐ Optional์ด ์๋ ๋ ์์ด ์์ผํ์ฌ U๋ก ๋ฆฌํด๋๋ ๊ฒ์ ๋ณผ ์ ์์
- rethrows
-
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 }
- ๋ญ๊ฐ ์ข๊ฒ ๋ง๋ค๊ณ ์ ๋ง๋ค์๋๋ฐ, convertor ์ฝ๋๊ฐ ์๋นํ ๋ณต์กํ๋ค.
- Error๋ฅผ ์ฒ๋ฆฌํ๋๋ฐ ์์ด flatMap์ด ๋ญ๊ฐ ๋ถ์กฑํ๋ค.
-
Custom transform function ์ถ๊ฐํ๊ธฐ
- Error๋ฅผ ์ฒ๋ฆฌํ๋ map
- Error Handling ์ถ๊ฐ
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:)) }
- Error๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ Result์ failure๋ก ๋ฃ์ด์ ๋๊ธด ๊ฒ์ด ๋ค์
- ํ์ง๋ง, ์ค์ ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ๊ฐ๊ฒฐ์ฑ์ด ๋งค์ฐ ํฅ์๋จ
- ๊ฐ๊ฐ์ ๋์ ์ญ์ ์์ํจ์๋ก ๋ถ๋ฆฌํ๋ค๋ฉด 6๋ฒ ์ฒ๋ผ ๊ตฌ์ฑ๋จ
-
ํจ๊ณผ
- Error handling์ด Result์์ผ๋ก ์จ์
- Error handling code๊ฐ ์ ๊ฑฐ๋์ด ์ฃผ logic์ ๊ฐ๋ฆฌ์ง ์์
- detailํ error๋ฅผ ์ถ๊ฐํ๊ธฐ๊ฐ ๋ ์ฌ์์ง
- Optional-flatMap ์กฐํฉ์์ ํ์ธ์ด ๋ถ๊ฐ๋ฅํ๋ ์ ํํ Error์์น์ ๋ด์ฉ์ด ๋จ์
- Error ํ์ ์ ๋ง๋ค์ด์ ๋ฐ์ ์ ์์!