LoginSignup
48
20

More than 3 years have passed since last update.

Combineでよく見るSwiftのNever型とは

Posted at

Never let me go — けして私を離さないで、僕たちはけしてと言うけれどその約束を守るのは難しい。
でも僕たちの愛する Swift では Never は絶対的な Never でその単純さが美しかった。

まぁそんなポエムはいいんですけど、iOS13から導入された Combine ではよく Never という型を見る。

@Published var x = 0
@Published var y = 0

var timesPublisher: AnyPublisher<Int, Never> {
    $x.combineLatest($y)
        .map(*)
        .eraseToAnyPublisher()
}

AnyPublisher<Int, Never> と失敗したときの型が Never になっている。これはけして失敗しないことを型で表現してる。

Publisher protocol の定義を見てみると publisher は失敗した場合の型を指定する必要がある。例え失敗しない場合でも型を指定する必要があるので失敗しないということを表す Never がここで必要になってくる。

public protocol Publisher {

    /// The kind of values published by this publisher.
    associatedtype Output

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    associatedtype Failure : Error

    /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
    ///
    /// - SeeAlso: `subscribe(_:)`
    /// - Parameters:
    ///     - subscriber: The subscriber to attach to this `Publisher`.
    ///                   once attached it can begin to receive values.
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

Uninhabited Types

Never という型がどういうものなのか関数を作ってみていく。

Screen Shot 2019-11-24 at 2.36.33 PM.png

型の宣言だけした関数を用意してみるとエラーで Int を返す関数は Int を返してないと言われるのに対して Never を返す関数のエラーは何やらいつものエラーと違うものになってる。

Function with uninhabited return type 'Never' is missing call to another never-returning function on all paths

uninhabited return type 'Never' というよくわからない説明をされる。
どうやら Never というのは普通の型とは違うらしい。

ここで Never の定義を見てみる。

public enum Never {}

普通の enum の定義だがそこには case が一つもなかった。
enum は init を持たないので case がないということは値を作ることができないということになる。

このように値を作れない型のことを uninhabited type という。

しかし値を作れない型が何の役に立つんだと思うかもしれない。
プログラムを書いてると値を返さない関数(無限ループ、プログラムの終了)を書きたくなることがある。Never があればそのような関数の型を正しく表現することができるようになる。

でも、値を返さない関数の時は Void で定義するじゃんと普通思う。

そこで Swift での Void が何なのか定義をみてみる。

public typealias Void = ()

Swift での Void は空のタプルの別名として定義されている。
そして Swift で関数定義する時に戻り値の型を定義しなかったら常に戻り値の型は Void になる。

func log(_ message: String) {
    print("Message: \(message)")
}

let logger: (String) -> Void = log

logger("This is void function")

空のタプルは () という型であり let unit = () として値を作ることができる。
このようにただ一つしか値がない型をユニット型という。なので () の型や値のことをユニットとよく呼ぶ。

つまり Swift での値を返さない関数と思っていた -> Void な関数は実際にはユニットを返してるのと同じ関数になる。

func f() -> Void {}
func g() { return () }

print() のように値を返さないと思っている関数は実は値を返している。

let printResult: Void = print("This is void function")
print(printResult)                  // => ()

NeverとVoidの関数の違い

値を返さない無限ループの関数を作ってみる。

func giveMeNever() -> Never {
    while true {}
}

この値を返さない関数を値を返す関数の中で呼んでみる。

func giveMeNever() -> Never {
    while true {}
}

func giveMeInt() -> Int {
    giveMeNever()
}

giveMeInt()Int の値を返してないけどエラーなくコンパイルすることができる。

giveMeNever() の代わりに Void を返す関数を呼んでみる。

Screen Shot 2019-11-24 at 7.54.45 PM.png

Cannot convert return expression of type '()' to return type 'Int'

()Int に変換できないとエラーになった。
Swift は強い型づけされた言語なので変換できないと言われる、なのに -> NeverInt を返すところで呼んでもエラーにならない。Never だけ特別扱いなのだろうか?まるで NeverInt として扱われてるみたいだ。他の型でもいいのだろうか? Double は? String は?

func giveMeDouble() -> Double {
    giveMeNever()
}

func giveMeString() -> String {
    giveMeNever()
}

エラーにならなかった。このコードは Swift では許可されたコードになる。
まるで NeverInt でもあるし Double, String でもあるようだ。

Never のように値を作ることができなくて、すべての型のサブ型のことを型理論ではボトム型という。

ボトム型

ボトム、底、底があるならこの世にはその逆の上の世界があると人は夢をみる。
型の世界にもボトムがあるようにトップがある。

ボトム型が全ての型のサブ型ならトップ型はその逆の全てのサブ型はトップ型のサブ型であるとなる。

Types.png

Swift には Any という型がある。
Any はあらゆる型の値を表すことができる型だ。例え関数でもだ。

func anyWeSayAnyType(_ x: Int) -> Any {
    switch x {
    case 4:
      return 12

    case 3:
      return 12.34

    case 2:
      return true

    case 1:
      return "This is String type but it's also Any"

    default:
      return { (x: Int, y: Int) in x * y }
    }
}

Any のようにあらゆる型の値を表せるのをトップ型という。
その逆のボトム型はあらゆる型のサブ型なので Int でも Double でも String としても扱える。

ボトム型のようでボトムじゃない Never

まるで NeverInt でもあるし Double, String でもあるようだ。

と前に言った。本当にそうなのだろうか?
コードで確かめてみよう。

Screen Shot 2019-11-25 at 1.44.30 PM.png

Never は定義した型に変換できないとエラーになる。
しかしクロージャで包んであげるとエラーにはならない。

func isNeverBottomType() {
    let x: Int = { giveMeNever() }()
    let flag: Bool = { giveMeNever() }()
    let text: String = { giveMeNever() }()
}

このように Swift 5.1 では Never は本物のボトム型ではない。
Never のような uninhabited type を関数の戻り値とする場合だけボトム型のような振る舞いをする。

一応 Swift の設計者は Never を Swift 3 で導入した時にボトム型にすることを提案はしていた。
Never as a universal "bottom" subtype

Neverによって得た型の表現力

Swift ではfatalError() という関数をよくみる。致命的なエラーだからプログラムを終了するという関数だ。

定義を見てみる。

public func fatalError(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never

Never を返してる。
しかし fatalError() は Swift 3以前は定義が違った。大体こんな定義だった。

@noreturn func fatalError(_ message: () -> String = String(), file: StaticString = #file, line: UInt = #line)

Never の代わりに @noreturn という注釈がつけられている。これはコンパイラにこの関数は戻り値がないということを教えている。
基本的には Never が使われてる意図と同じだ。

しかし @noreturn にはいくつか問題点があった。
次のような使い方をしたらどうなるだろうか?

@noreturn func square(_ x: Int) -> Int { ... }
@noreturn func runApp() throws { ... }

値を返す関数やエラーを投げる関数に @noreturn をつけたらどうすればいいだろうか?
コンパイラは最初の値を返す場合はエラーにしたらいいだろう、でもエラーを投げる場合は許可すべきだろうか?許可はするけど値は返さないとすればいいだろうか?それとも値を返さないしエラーを投げることもないとすればいいだろうか?明らかな正解はない。
注釈による戻り値がないことを表すとこのように複雑性が生まれる。

Swift はこの問題を Never を導入することによって解決した。
Never という値を作れない型で値を返せないことを型として表現した。とても単純で美しい解決方法だ。

おわりに

ここまで読んでくださってありがとうございます。

Never というとても小さく単純な型をみてきた。
小さく単純なものを定義することによってプログラミングでよくある問題を表現することができるのは美しいと思う。

Never return Never

48
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
20