割り当て後に汎用パラメーター「T」を推測できませんでした
// Xcode 11.6 / Swift 5
import Foundation
func f<T>(_: (T) -> Void) { }
@discardableResult
func g(_: Int) -> Int { 0 }
f { g($0) } // compiles fine f { let _ = g($0) } // Generic parameter 'T' could not be inferred
上記のコードでは、汎用関数f
はその引数として型の引数を取る関数を期待していますT
。
この関数g
は、型の引数を取りますInt
。
を書くf { g($0) }
と、コードがコンパイルされます。私は信じているコンパイラはそれを推測することができますので、このコンパイルを(私が間違っている場合は、私を修正してください)T
ですInt
基づいてg
の引数の型。
ただし、g
たとえばlet _ = g($0)
行で、の戻り値を使用して何かを実行しようとすると、コンパイラは、のタイプを推測できなくなったと文句を言いT
ます。
の戻り型はg
、コンパイラがT
の型を推測する方法とは関係がないように思われますが、明らかに関係があります。
なぜこれが起こるのか、そしてそれを修正するために(もしあれば)何ができるのか、誰かが光を当てることができますか?
回答
これはコンパイラのバグである場合とそうでない場合があります。
SR-1570で述べられているように、Swiftはいくつかのクロージャ、つまりマルチステートメントのクロージャのタイプを推測しようとしないことが知られています。
これは正しい動作です。Swiftは、マルチステートメントクロージャの本体からパラメータを推測したり型を返したりしません。
ただし、クロージャは1つのステートメント、具体的には1つの宣言のみで構成されます。奇妙ではありますが、クロージャーに1つの宣言も含まれている場合に、Swiftが型を推測しようとしないように設計されている可能性があります。たとえば、これもコンパイルされません
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
これが設計によるものである場合、その背後にある理論的根拠は、クロージャ内の単一の宣言がとにかくあまり意味をなさないためである可能性があります。宣言されたものは何でも使用されません。
しかし、繰り返しになりますが、これは単なる推測であり、これもバグである可能性があります。
これを修正するには、単に宣言ではないものにします。
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
または
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
この関数f
は、関数をパラメーターとして受け取ります。パラメーターは、型のパラメーターを受け取り、T
何も返しません(Void
)。型を自動的に推測するクロージャの場合、単一の(場合によっては単純な)式で構成されている必要があります。複雑なものがあると、コンパイラーが推測するのが難しくなります(これはコンパイラーの観点からは理にかなっています)。どうやら、let _ = g($0)
コンパイラに関する限り、これは複雑なステートメントです。詳細については、このメーリングリストのディスカッションを参照してください。
@discardableResultを使用すると、次の2種類の関数を使用できるようになります。
g(_:Int)-> Intおよびg(_:Int)-> Void(関数の結果を使用したくない場合)
私はそう思います
f { g($0) }
-ここで、fは同じ型を持っているため、型を推測できます
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
-この場合、g関数のタイプはf関数とは異なります
(_: (T) -> Void) and (_: (Int) -> Int)
「let」を削除すると、再度コンパイルされます。
f { _ = g($0) }
私はそれを考えるf { let _ = g($0) }
-リターンのみのInt値f { _ = g($0) }
-リターン機能(_:(INT) - > INT)
多分それはここでの鍵です