我想在我的Swift项目中创建一些函数,可以接受对象,也可以接受返回该对象类型的闭包。当然,我可以在每个地方用多个签名定义同一个函数,但那太啰嗦了。我也希望能够创建这些对象/对象返回闭包的类型安全列表,但如果没有描述这两件事的通用类型,就无法做到这一点。
这就是我想要做的
typealias StringClosure = () -> String
protocol Stringable {
func toStringClosure() -> StringClosure
}
extension String : Stringable {
func toStringClosure() -> StringClosure {
return { return self }
}
}
extension StringClosure : Stringable {
func toStringClosure() -> StringClosure {
return self
}
}
func printStringable(a : Stringable) {
print(a.toStringClosure()())
}
var stringableList : Stringable[] = ["cat", {return "dog"}, "gecko"]
for stringable in StringableList {
printStringable(stringable)
}
但这不起作用,因为我实际上不能扩展我的StringClosure
类型来实现Stringable
。我可以使stringableList
成为Any
类型的列表,但这不是类型安全的。
枚举解
一个解决方案是,我可以创建一个枚举类型,但这意味着我必须在使用枚举的任何地方显式注释这些类型,这很蹩脚。它看起来像这样:
enum StringableEnum {
case Str(String)
case Fun(StringClosure)
}
func printStringableEnum(a : StringableEnum) {
switch (a) {
case let .Str(value):
print(value)
case let .Fun(value):
print(value())
}
}
var enumList : StringableEnum[] = [.Str("cat"), .Fun({return "dog"}), .Str("gecko")]
for element in enumList {
printStringableEnum(element)
}
这还不错,但它要求我的API的用户现在知道这个enum,并在每次调用我的printStringableEnum
函数时用.Str
或.Fun
标记他们的参数。这不是一个很好的API!
这可能对语言要求太高了,但是有人有更好的想法吗?
我认为在这种情况下,最简单的解决方案可能是最好的。不允许直接传入string,而是要求使用闭包。将字符串转换为闭包并不难:
let myString = "Hello"
printStringable({return myString})
为了方便,你甚至可以创建一个函数将值转换成闭包:
func f<T>(value : T) -> () -> T {
return {return value}
}
printStringable(f("Hello"))
printStringable(f(myString))
虽然我不认为保存的几个字符是值得的,但可能令人困惑的函数名。
编辑:
还可以这样改进枚举:
enum StringableEnum {
case Str(String)
case Fun(() -> String)
init(_ string : String) {
self = .Str(string)
}
init(_ closure : () -> String) {
self = .Fun(closure)
}
var value : String {
switch(self) {
case let .Str(value):
return value
case let .Fun(closure):
return closure()
}
}
}
这意味着您可以从任何支持的类型创建枚举,如下所示:
var stringable = StringableEnum("Hello")
stringable = StringableEnum({return "Hello"})
你可以通过
把字符串取出来stringable.value
EDIT:不要做以下事情。它可以在Beta 4中使用,但依赖于一个苹果不希望公众使用的功能,并打算在Swift 1.0发布之前删除。
@auto_closure
可能适合您,但问题是,似乎您不能将显式闭包传递给@auto_closure
参数。
另一种选择是将所有内容都视为闭包,并使用Swift的__conversion
特性在需要的地方隐式地将字符串转换为stringclosure:
typealias StringClosure = () -> String
extension String {
func __conversion() -> StringClosure {
return { self } // 'return' can be omitted inside a single-expression closure
}
}
// type inferencing automatically figures out that
// stringableList should be [StringClosure] and applies
// the conversion method to promote the string entries into
// self-returning closures
var stringableList = ["cat", { println("side-effects are bad"); return "dog"}, "gecko"]
func printStringClosure(s: StringClosure) {
println(s())
}
for s in stringableList {
printStringClosure(s)
}
printStringClosure("test")
printStringClosure { let a = 5*5; return "(a)" }
不要忘记swift有创建惰性计算变量的选项,就像这样(它们不限于类/结构):
var lazy : String {
return "Hello"
}
printStringable(lazy)
如果你不需要参数化的闭包,这是一个更简单的解决你的延迟计算/闭包有副作用的问题。