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
という型がどういうものなのか関数を作ってみていく。
型の宣言だけした関数を用意してみるとエラーで 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
を返す関数を呼んでみる。
Cannot convert return expression of type '()' to return type 'Int'
()
は Int
に変換できないとエラーになった。
Swift は強い型づけされた言語なので変換できないと言われる、なのに -> Never
を Int
を返すところで呼んでもエラーにならない。Never
だけ特別扱いなのだろうか?まるで Never
が Int
として扱われてるみたいだ。他の型でもいいのだろうか? Double
は? String
は?
func giveMeDouble() -> Double {
giveMeNever()
}
func giveMeString() -> String {
giveMeNever()
}
エラーにならなかった。このコードは Swift では許可されたコードになる。
まるで Never
は Int
でもあるし Double
, String
でもあるようだ。
Never
のように値を作ることができなくて、すべての型のサブ型のことを型理論ではボトム型という。
ボトム型
ボトム、底、底があるならこの世にはその逆の上の世界があると人は夢をみる。
型の世界にもボトムがあるようにトップがある。
ボトム型が全ての型のサブ型ならトップ型はその逆の全てのサブ型はトップ型のサブ型であるとなる。
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
まるで
Never
はInt
でもあるしDouble
,String
でもあるようだ。
と前に言った。本当にそうなのだろうか?
コードで確かめてみよう。
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