Optional和Function
在InfoQ上看唐巧推荐了一篇关于Swift中monad的文章
看了之后对于Optional类型能用map和flatMap很是兴奋, 于是拉上同事开始对项目中大量的if let ... else进行重写, 然后在重写中发现了一个有趣的问题. 先看下optional中map和flatMap的函数定义:
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
区别就在接收的参数f的定义不同. map的参数f的返回值不是Optional, 而flatMap的参数f的返回值是Optional. 但是看下面代码:
let f: Int -> Int = { $0 }
let s: Int? = 1
let res = s.flatMap(f) // 没有报错!
什么情况, flatMap接收的不应该是Int -> Int?类型么? 继续往下看:
if f is (Int -> Int?) { // 编译器warning: 'is' test is always true
print("hehe")
} else {
print("no") // 运行时 进入此分支, 打印no
}
编译时警告和运行结果不一样…我在so上问了这个问题: question 等待答案. 个人倾向这个是bug… Anyway, 先不管这个问题, 看下面的代码:
class A {}
class B: A {}
func f(a: A?) -> B { return B() }
func test(f: B -> A?) {
let b = B()
print(f(b))
}
test(f)
接受一个B -> A?的参数, 结果传入一个A? -> B的参数都没问题.
首先解释关于optional部分的问题. 我们都知道如果一个函数的返回值是optional, 例如Int?, 我们let i: Int = 1; return i是没有问题的. 这里的i的类型是Int而不是Int?, 但是编译时不报错, 运行时也没有问题. 这说明Swift对于optional进行了优化, 至于是编译时还是运行时呢, 是编译时的. 例如下面的代码:
func f(a: String?) -> String? {
let x = "hehe"
return x
}
let a = "yupengyang"
f(a)
在命令行用swiftc -emit-sil your_swfit_file就可以看到编译后的中间语言. 先看调用时, 函数参数是optional而传入的不是optional的情况
let a = "yupengyang"
f(a)
// 这两行对应的中间语言大概如下:
%12 = global_addr @_Tv4test1aSS : $*String // users: %19, %21
// function_ref Swift.String.init (_builtinStringLiteral : Builtin.RawPointer, byteSize : Builtin.Word, isASCII : Builtin.Int1) -> Swift.String
%13 = function_ref @_TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %18
%14 = metatype $@thin String.Type // user: %18
%15 = string_literal utf8 "yupengyang" // user: %18
%16 = integer_literal $Builtin.Word, 10 // user: %18
%17 = integer_literal $Builtin.Int1, -1 // user: %18
%18 = apply %13(%15, %16, %17, %14) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %19
store %18 to %12 : $*String // id: %19
//------------到此为止创建了String "yupengyang"
// function_ref test.f (Swift.Optional<Swift.String>) -> Swift.Optional<Swift.String>
%20 = function_ref @_TF4test1fFGSqSS_GSqSS_ : $@convention(thin) (@owned Optional<String>) -> @owned Optional<String> // user: %24
%21 = load %12 : $*String // users: %22, %23
retain_value %21 : $String // id: %22
%23 = enum $Optional<String>, #Optional.Some!enumelt.1, %21 : $String // user: %24
//------------上面这一句就是把String转成了Optional<String>了
%24 = apply %20(%23) : $@convention(thin) (@owned Optional<String>) -> @owned Optional<String> // user: %25
在看函数定义的返回值类型是optional, 而实际返回的类型不是optional的情况, 函数f对应的中间代码摘取如下
debug_value %0 : $Optional<String>, let, name "a", argno 1 // id: %1
// function_ref Swift.String.init (_builtinStringLiteral : Builtin.RawPointer, byteSize : Builtin.Word, isASCII : Builtin.Int1) -> Swift.String
%2 = function_ref @_TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
%3 = metatype $@thin String.Type // user: %7
%4 = string_literal utf8 "hehe" // user: %7
%5 = integer_literal $Builtin.Word, 4 // user: %7
%6 = integer_literal $Builtin.Int1, -1 // user: %7
%7 = apply %2(%4, %5, %6, %3) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // users: %8, %9
debug_value %7 : $String, let, name "x" // id: %8
//----以上部分创建了变量x, 也就是我们要返回的String
%9 = enum $Optional<String>, #Optional.Some!enumelt.1, %7 : $String // user: %11
//----这一句把String转成了Optional<String>了
release_value %0 : $Optional<String> // id: %10
return %9 : $Optional<String> // id: %11
//----返回Optional<String>
func f(a: String?) -> String? {
let x = "hehe"
return x
}
let a = "yupengyang"
f(a)
在命令行用swiftc -emit-sil your_swfit_file就可以看到编译后的中间语言. 先看调用时, 函数参数是optional而传入的不是optional的情况
let a = "yupengyang"
f(a)
// 这两行对应的中间语言大概如下:
%12 = global_addr @_Tv4test1aSS : $*String // users: %19, %21
// function_ref Swift.String.init (_builtinStringLiteral : Builtin.RawPointer, byteSize : Builtin.Word, isASCII : Builtin.Int1) -> Swift.String
%13 = function_ref @_TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %18
%14 = metatype $@thin String.Type // user: %18
%15 = string_literal utf8 "yupengyang" // user: %18
%16 = integer_literal $Builtin.Word, 10 // user: %18
%17 = integer_literal $Builtin.Int1, -1 // user: %18
%18 = apply %13(%15, %16, %17, %14) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %19
store %18 to %12 : $*String // id: %19
//------------到此为止创建了String "yupengyang"
// function_ref test.f (Swift.Optional<Swift.String>) -> Swift.Optional<Swift.String>
%20 = function_ref @_TF4test1fFGSqSS_GSqSS_ : $@convention(thin) (@owned Optional<String>) -> @owned Optional<String> // user: %24
%21 = load %12 : $*String // users: %22, %23
retain_value %21 : $String // id: %22
%23 = enum $Optional<String>, #Optional.Some!enumelt.1, %21 : $String // user: %24
//------------上面这一句就是把String转成了Optional<String>了
%24 = apply %20(%23) : $@convention(thin) (@owned Optional<String>) -> @owned Optional<String> // user: %25
在看函数定义的返回值类型是optional, 而实际返回的类型不是optional的情况, 函数f对应的中间代码摘取如下
debug_value %0 : $Optional<String>, let, name "a", argno 1 // id: %1
// function_ref Swift.String.init (_builtinStringLiteral : Builtin.RawPointer, byteSize : Builtin.Word, isASCII : Builtin.Int1) -> Swift.String
%2 = function_ref @_TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
%3 = metatype $@thin String.Type // user: %7
%4 = string_literal utf8 "hehe" // user: %7
%5 = integer_literal $Builtin.Word, 4 // user: %7
%6 = integer_literal $Builtin.Int1, -1 // user: %7
%7 = apply %2(%4, %5, %6, %3) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // users: %8, %9
debug_value %7 : $String, let, name "x" // id: %8
//----以上部分创建了变量x, 也就是我们要返回的String
%9 = enum $Optional<String>, #Optional.Some!enumelt.1, %7 : $String // user: %11
//----这一句把String转成了Optional<String>了
release_value %0 : $Optional<String> // id: %10
return %9 : $Optional<String> // id: %11
//----返回Optional<String>
由此可见, swift是在编译时期就做了这个转换的.
下面回头我们最开始的例子, 参数是闭包的情况. 上面的代码中, test函数接收的参数是B -> A?的一个闭包. 我们可以传入B? -> A的闭包, 但是如果test接收的是B? -> A的闭包, 我们不能传入B -> A的闭包, 如下:
class A {}
class B: A {}
func test1(f: B? -> A?) {
let b: B? = nil
f(b)
}
下面解释下原因. 看test1函数, 传入的闭包实际上是要在test1中被调用的, 在上面的代码test1函数里面, 变量b的类型是B?, 闭包f的类型是B? -> A?, 这个时候调用f(b), 编译器进行类型检查, 发现b的类型和闭包f的入参类型是一致的, 没有问题. 如果我们传入的f的类型是B -> A?, 那么调用f(b)的时候, 编译器会发现, Optional<B>类型的b要传入接收B类型的闭包f, 编译器不能从Optional<B>转成B, 所以编译时报错.