mike-neckのブログ

Java or Groovy or Swift or Golang

Swift の Result 型メモ

わけあって、 swift-nio-ssl を急遽触っているが、その際に Swift5 の Result 型をさわったので、そのメモ。

f:id:mike_neck:20180609045321p:plain

ソースコードGitHubにあるので、この説明よりはソースを読むほうが速い。

github.com


型の詳細

Result のコードを見てみると次のように定義されています。

public enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)
}

これは、 haskellEither l r のように、 Success または Failure: Error のどちらか一方を保持可能な型

ハンドリング

swift-nio の NIO2 では EventLoopFuture<Value>whenComplete((Result<Value,Error>) -> Void) というメソッドが追加されており、成功であろうが、失敗であろうがハンドリングが可能になっている。というわけで、こちらを例にエラーハンドリングを行う。

let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)

let promise: EventLoopPromise<String> = eventLoopGroup.next().makePromise(String.self)

promise.futureResult.whenComplete({ result in
  switch result {
  case let .success(item): print(item)
  case let .failure(err): print(err)
})

promise.succeed("hello")

これを実行すると次のように表示される。

hello

また、次のように Error を渡せる。

enum Err: Error {
  case err(_ message: String)
}

let promise: EventLoopPromise<String> = eventLoopGroup.next().makePromise(String.self)

promise.futureResult.whenComplete({ result in
  switch result {
  case let .success(item): print(item)
  case let .failure(err): print(err)
})

promise.fail(Err.err("hello"))

これを実行すると次のように表示される。

err("hello")

map/mapError/flatMap/flatMapError

Result には map/mapError/flatMap/flatMapError というメソッドがついている。

map

mapResult<Success, Failure>Success を異なる NewSuccess に変換するメソッド。

promise.futureResult.whenComplete({ result in
  let r = result.map { (str: String) -> Int in str.utf8.count }
  print(r)
})

promise.succeed("foo-bar")

これは次のように表示される

success(7)

flatMap

flatMapResult<Success, Failure>Success を別の Result<NewSuccess, Failure> に変換するメソッド。

enum Err: Error {
    case err(_ message: String)
}

enum Err2: Error {
    case err2(String)
}

let result: Result<String, Error> = Result.success("foo")
let r = result.flatMap { (str: String) -> Result<Int, Error> in .failure(Err2.err2(str)) }

このコードの実行結果は次のようになる

failure(MyModule.Err2.err2("foo"))

なお、新しい Result<NewSuccess, Failure>Failure の方は型を変えられないので注意が必要。例えば、次のコードはエラーとなる。

enum Err: Error {
  case err(String)
}
 
enum Err2: Error {
  case err2(String)
}

let result: Result<String, Err> = .success("foo")
let r = result.flatMap { (str: String) -> Result<Int, Error> in .failure(Err2.err2(str)) }

結果

main.swift:10:24: error: cannot convert value of type '(String) -> Result<Int, Error>' to expected argument type '(String) -> Result<_, Err>'
let r = result.flatMap { (str: String) -> Result<Int, Error> in .failure(Err2.err2(str)) }
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

mapError

mapErrormapFailure 版。 Failure を別の異なる NewFailure に変換する

enum Err: Error {
    case err(_ message: String)
}

enum Err2: Error {
    case err2(String)
}

let result: Result<String, Err> = Result.failure(.err("foo"))
let r = result.mapError { (e: Err) -> Err2 in
  switch e {
  case let .err(msg): return .err2(msg)
  }
}

実行結果

failure(MyModule.Err2.err2("foo"))

flatMapError

flatMapErrorflatMapFailure 版。 Success の型は変えられない。

enum Err: Error {
  case err(_ message: String)
}

enum Err2: Error {
  case err2(String)
}

p2.succeed("foo-bar")

let result: Result<String, Err> = Result.failure(.err("foo"))
let r = result.flatMapError { (e: Err) -> Result<String, Err2> in
  switch e {
  case let .err(msg): return .success(msg)
  }
}

実行結果

success("foo")

その他

  • Success を取り出すか、 Failure を投げる get というメソッドがある。
  • let result: Result<String, Error> = Result(catching: { "foo" }) というイニシャライザーがある。

おわり