ไม่สามารถอนุมานพารามิเตอร์ทั่วไป '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
ประเภทของ แต่อย่างชัดเจน
ใครช่วยให้ความกระจ่างว่าทำไมสิ่งนี้ถึงเกิดขึ้นและจะทำอะไรได้บ้าง (ถ้ามี) เพื่อแก้ไข
คำตอบ
นี่อาจเป็นบั๊กของคอมไพเลอร์หรือไม่ก็ได้
เป็นที่ทราบกันดีว่า Swift ไม่ได้พยายามอนุมานประเภทของการปิดบางประเภทกล่าวคือคำสั่งหลายคำตามที่กล่าวไว้ใน SR-1570 :
นี่เป็นพฤติกรรมที่ถูกต้อง: Swift ไม่อนุมานพารามิเตอร์หรือส่งคืนประเภทจากเนื้อความของการปิดหลายคำสั่ง
อย่างไรก็ตามการปิดบัญชีของคุณประกอบด้วยคำแถลงเดียวคำประกาศเดียวที่เจาะจง เป็นไปได้แม้ว่าจะแปลกที่พวกเขาออกแบบมาเพื่อให้ 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)
บางทีมันอาจจะเป็นกุญแจสำคัญที่นี่