Parameter umum 'T' tidak dapat disimpulkan setelah penugasan

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

Dalam kode di atas, fungsi generik fmengharapkan sebagai argumennya fungsi yang mengambil argumen tipe T.

Fungsi gmengambil 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 Tadalah Intberdasarkan gtipe 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 gseharusnya tidak ada hubungannya dengan bagaimana kompilator menyimpulkan Ttipe itu, tetapi jelas itu terjadi.

Adakah yang bisa menjelaskan mengapa hal ini terjadi, dan apa (jika ada) yang dapat dilakukan untuk memperbaikinya?

Jawaban

2 Sweeper Aug 18 2020 at 11:45

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
2 Malik Aug 18 2020 at 11:44

Fungsi tersebut fmengambil fungsi sebagai parameter yang pada gilirannya mengambil parameter tipe Tdan 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

AlexanderNikolaychuk Aug 18 2020 at 11:44

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