Swift flatMap on array with elements are optional has different behavior -
let arr: [int?] = [1,2,3,4,nil] let arr1 = arr.flatmap { next in next } // arr1: [1,2,3,4] let arr2: [int?] = arr.flatmap { next -> int? in next } // arr2: [optional(1), optional(2), optional(3), optional(4)]
i'm confused these code, why make difference?
update: please see these codes,
let arr: [int?] = [1,2,3,4,nil] let arr1: [int?] = arr.flatmap { next in next } // arr1: [optional(1), optional(2), optional(3), optional(4), nil] let arr2: [int?] = arr.flatmap { next -> int? in next } // arr2: [optional(1), optional(2), optional(3), optional(4)]
as @adam says, it's due explicit type you're supplying result. in second example, leading confusion caused double wrapped optionals. better understand problem, let's take @ flatmap
function signature.
@warn_unused_result public func flatmap<t>(@noescape transform: (self.generator.element) throws -> t?) rethrows -> [t]
when explicitly specify result of type [int?]
, because flatmap
returns generic type [t]
– swift infer t
int?
.
now causes confusion because closure pass flatmap
takes element input , returns t?
. because t
int?
, closure going returning t??
(a double wrapped optional). compiles fine because types can freely promoted optionals, including optionals being promoted double optionals.
so what's happening int?
elements in array getting promoted int??
elements, , flatmap
unwrapping them down int?
. means nil
elements can't filtered out arr1
they're getting doubly wrapped, , flatmap
operating on second layer of wrapping.
why arr2
is able have nil
filtered out of appears result of promotion of closure pass flatmap
. because explicitly annotate return type of closure int?
, closure implicitly promoted (element) -> int?
(element) -> int??
(closure return types can freely promoted in same way other types) – rather element itself being promoted int?
int??
, without type annotation closure inferred (element) -> int??
.
this quirk appears allow nil
avoid being double wrapped, , therefore allowing flatmap
filter out (not entirely sure if expected behaviour or not).
you can see behaviour in example below:
func optionalintarraywithelement(closure: () -> int??) -> [int?] { let c = closure() // of type int?? if let c = c { // of type int? return [c] } else { return [] } } // quirk: if don't explicitly define type of optional (i.e write 'nil'), // nil won't double wrapped in either circumstance let elementa : () -> int? = {optional<int>.none} // () -> int? let elementb : () -> int?? = {optional<int>.none} // () -> int?? // (1) nil gets picked if let, closure gets implicitly upcast () -> int? () -> int?? let arr = optionalintarraywithelement(elementa) // (2) nil doesn't picked if let element gets promoted double wrapped optional let arr2 = optionalintarraywithelement(elementb) if arr.isempty { print("nil filtered out of arr") // prints } if arr2.isempty { print("nil filtered out of arr2") // doesn't print }
moral of story
steer away double wrapped optionals, can give super confusing behaviour!
if you're using flatmap
, should expecting [int]
if pass in [int?]
. if want keep optionality of elements, use map
instead.
Comments
Post a Comment