ไม่สามารถอนุมานพารามิเตอร์ทั่วไป 'T' ได้หลังการกำหนด

Aug 18 2020
// 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

ในโค้ดข้างต้นฟังก์ชั่นทั่วไปคาดว่าเป็นอาร์กิวเมนต์ของฟังก์ชั่นที่ใช้อาร์กิวเมนต์ชนิดอีกด้วยfT

ฟังก์ชั่นใช้อาร์กิวเมนต์พิมพ์gInt

เมื่อฉันเขียนf { g($0) }โค้ดจะคอมไพล์ ผมเชื่อว่า (โปรดถูกต้องฉันหากฉันผิด) คอมไพล์นี้เพราะคอมไพเลอร์สามารถอนุมานได้ว่าTเป็นIntอยู่บนพื้นฐานgของประเภทการโต้แย้ง

อย่างไรก็ตามเมื่อฉันพยายามทำบางอย่างกับค่าส่งคืนของgตัวอย่างเช่นในlet _ = g($0)บรรทัดคอมไพลเลอร์บ่นว่าไม่สามารถอนุมานประเภทของT.

สำหรับฉันแล้วประเภทการส่งคืนgไม่ควรมีผลต่อวิธีการที่คอมไพเลอร์อนุมานTประเภทของ แต่อย่างชัดเจน

ใครช่วยให้ความกระจ่างว่าทำไมสิ่งนี้ถึงเกิดขึ้นและจะทำอะไรได้บ้าง (ถ้ามี) เพื่อแก้ไข

คำตอบ

2 Sweeper Aug 18 2020 at 11:45

นี่อาจเป็นบั๊กของคอมไพเลอร์หรือไม่ก็ได้

เป็นที่ทราบกันดีว่า 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
2 Malik Aug 18 2020 at 11:44

ฟังก์ชันfรับฟังก์ชันเป็นพารามิเตอร์ซึ่งจะรับพารามิเตอร์ประเภทTและส่งกลับค่าอะไร ( Void) สำหรับการปิดเป็นประเภทอนุมานโดยอัตโนมัติจะต้องประกอบด้วยนิพจน์เดียว (และบางครั้งก็เรียบง่าย) อะไรก็ตามที่ซับซ้อนทำให้คอมไพเลอร์อนุมานได้ยาก (ซึ่งสมเหตุสมผลจากจุดยืนของคอมไพเลอร์) เห็นได้ชัดว่าlet _ = g($0)เป็นคำสั่งที่ซับซ้อนเท่าที่คอมไพเลอร์เกี่ยวข้อง สำหรับข้อมูลเพิ่มเติมโปรดดูการอภิปรายเกี่ยวกับรายชื่ออีเมลนี้

AlexanderNikolaychuk Aug 18 2020 at 11:44

ดูเหมือนว่า @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)

บางทีมันอาจจะเป็นกุญแจสำคัญที่นี่