์ˆœ์ˆ˜ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์–ธ์–ด 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๋กœ ๋ฆฌํ„ด๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Œ
  • 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 ํƒ€์ž…์„ ๋งŒ๋“ค์–ด์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ!