Обертки параметров Swift @autoclosure при условии явного закрытия
Рассмотрим следующую функцию:
func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}
Естественно, мы можем вызвать это так:
whatever(foo: 5)
// prints: 5
Однако предоставление явного аргумента замыкания заставляет компилятор жаловаться:
whatever(foo: { 5 })
// Error: Function produces expected type 'Int'; did you mean to call it with '()'?
Это предназначено? Чтение документации для @autoclosure
Я не нашел утверждения о том, всегда ли аргументы оборачиваются, даже когда предоставляется закрытие. Мое понимание @autoclosure
было:
Возьмите аргумент закрытия. Если аргумент не является замыканием, но имеет тот же тип, что и возвращаемое замыкание, оберните его.
Тем не менее, поведение, которое я вижу, таково: заверните аргумент, несмотря ни на что
Более сложный пример делает это странным для меня:
struct Defaults {
static var dispatcher: Defaults = ...
subscript<T>(setting: Setting<T>) -> T { ... }
struct Setting<T> {
let key: String
let defaultValue: () -> T
init(key: String, defaultValue: @escaping @autoclosure () -> T) {
self.key = key
self.defaultValue = defaultValue
}
}
}
extension Defaults.Setting {
static var nickname: Defaults.Setting<String> {
return Defaults.Setting(key: "__nickname", defaultValue: "Angela Merkel")
}
}
// Usage:
Defaults.dispatcher[.nickname] = "Emmanuel Macron"
Теперь скажем, я хочу хешировать ключ Setting
значение:
extension Defaults.Setting {
var withHashedKey: Defaults.Setting<T> {
return Defaults.Setting(key: key.md5(), defaultValue: defaultValue)
// Error: Cannot convert return expression of type 'Defaults.Setting<() -> T>' to return type 'Defaults.Setting<T>'
}
}
Чтобы уточнить: defaultValue
имеет тип () -> T
, Предоставляя это init(key: String, defaultValue: () -> T)
, в моем ожидании должен просто работать, потому что аргумент и параметр имеют одинаковый тип (в то время как параметр @autoclosure
).
Тем не менее, Swift, кажется, оборачивает предоставленное закрытие, эффективно создавая () -> () -> T
, который создает Setting<() -> T>
вместо Setting<T>
,
Я могу обойти эту проблему, объявив init
который принимает явно не @autoclosure
параметр:
extension Defaults.Setting {
init(key: String, defaultValue: @escaping () -> T) {
self.init(key: key, defaultValue: defaultValue)
}
}
Что действительно пугает, так это то, что я могу просто переслать init
принимая @autoclosure
параметр, и это работает.
Я что-то здесь упускаю, или это просто невозможно в Swift предоставить аргументы закрытия @autoclosure
параметры?
1 ответ
Swift ожидает, что вы передадите выражение, которое приводит к Int
в whatever(foo:)
и Swift обернет это выражение в замыкание типа () -> Int
,
func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}
Когда вы называете это так:
func whatever(foo: {5})
Вы передаете выражение, которое приводит к () -> Int
а не Int
Свифт ожидает. Вот почему он предлагает вам добавить ()
и вызвать это замыкание, чтобы получить выражение, которое возвращает Int
:
func whatever(foo: {5}())
Обратите внимание, что, поскольку Swift обертывания {5}()
в закрытии, он не оценивается перед вызовом whatever(foo:)
но на самом деле звонок задерживается, пока вы не оцените let x = foo()
,
Вы можете проверить это, запустив это на игровой площадке:
func whatever(foo: @autoclosure () -> Int) {
print("inside whatever")
let x = foo()
print(x)
let y = foo()
print(y)
}
whatever(foo: { print("hi"); return 3 }())
Выход:
inside whatever hi 3 hi 3
Если ты хочешь whatever(foo:
чтобы иметь возможность взять () -> Int
закрытие, перегрузка и вызов версии автозаполнения после вызова foo
:
func whatever(foo: @autoclosure () -> Int) {
print("autoclosure whatever")
let x = foo()
print(x)
}
func whatever(foo: () -> Int) {
print("closure whatever")
whatever(foo: foo())
}
whatever(foo: { print("two"); return 6 })
whatever(foo: { print("two"); return 6 }())
Выход:
closure whatever autoclosure whatever two 6 autoclosure whatever two 6