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

Popular posts from this blog

ios - RestKit 0.20 — CoreData: error: Failed to call designated initializer on NSManagedObject class (again) -

java - Digest auth with Spring Security using javaconfig -

laravel - PDOException in Connector.php line 55: SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES) -