Parameter umum 'T' tidak dapat disimpulkan setelah penugasan
// 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
Dalam kode di atas, fungsi generik f
mengharapkan sebagai argumennya fungsi yang mengambil argumen tipe T
.
Fungsi g
mengambil argumen tipe Int
.
Saat saya menulis f { g($0) }
, kode dikompilasi. Saya percaya (tolong koreksi saya jika saya salah) kompilasi ini karena kompilator dapat menyimpulkan bahwa itu T
adalah Int
berdasarkan g
tipe argumen.
Namun, ketika saya mencoba melakukan sesuatu dengan nilai kembalian g
, misalnya di let _ = g($0)
baris, kompilator mengeluh bahwa ia tidak dapat lagi menyimpulkan jenis T
.
Tampaknya bagi saya tipe kembalian g
seharusnya tidak ada hubungannya dengan bagaimana kompilator menyimpulkan T
tipe itu, tetapi jelas itu terjadi.
Adakah yang bisa menjelaskan mengapa hal ini terjadi, dan apa (jika ada) yang dapat dilakukan untuk memperbaikinya?
Jawaban
Ini mungkin atau mungkin bukan bug kompilator.
Diketahui bahwa Swift tidak mencoba menyimpulkan tipe dari beberapa closure, yaitu multi-statement, seperti yang dikatakan dalam SR-1570 :
Ini adalah perilaku yang benar: Swift tidak menyimpulkan parameter atau tipe kembalian dari badan penutupan multi-pernyataan.
Namun, penutupan Anda hanya terdiri dari satu pernyataan, satu deklarasi lebih spesifik. Mungkin saja, meskipun aneh, mereka mendesainnya sehingga Swift tidak mencoba menyimpulkan tipe jika closure berisi satu deklarasi juga. Misalnya, ini juga tidak dapat dikompilasi
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Jika ini disengaja, alasan di baliknya mungkin karena satu deklarasi dalam penutupan tidak masuk akal. Apa pun yang dideklarasikan, tidak akan digunakan.
Tapi sekali lagi, ini hanya spekulasi, dan ini bisa jadi bug juga.
Untuk memperbaikinya, cukup jadikan itu bukan deklarasi:
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
Atau
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
Fungsi tersebut f
mengambil fungsi sebagai parameter yang pada gilirannya mengambil parameter tipe T
dan mengembalikan nothing ( Void
). Untuk closure untuk menyimpulkan tipe secara otomatis, ia harus terdiri dari ekspresi tunggal (dan terkadang sederhana). Segala sesuatu yang kompleks akan mempersulit kompilator untuk menyimpulkan (yang masuk akal dari sudut pandang kompiler). Rupanya, let _ = g($0)
ini adalah pernyataan yang kompleks sejauh menyangkut kompilator. Untuk informasi lebih lanjut, lihat diskusi milis ini
sepertinya @discardableResult memberi Anda kemampuan untuk memiliki 2 jenis fungsi:
g (_: Int) -> Int dan g (_: Int) -> Void (ini adalah saat Anda tidak ingin menggunakan hasil dari fungsi)
Saya pikir begitu
f { g($0) }
- di sini f Anda dapat menyimpulkan suatu tipe karena memiliki Tipe yang sama
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- dalam hal ini jenis fungsi g berbeda dengan fungsi f
(_: (T) -> Void) and (_: (Int) -> Int)
Jika Anda akan menghapus "biarkan" itu akan dikompilasi lagi:
f { _ = g($0) }
Saya pikir f { let _ = g($0) }
- hanya mengembalikan nilai Int f { _ = g($0) }
- fungsi pengembalian (_: (Int) -> Int)
Mungkin itu kuncinya di sini