Why Does Compactmap Return a Nil Result

Why does compactMap return a nil result?

This is because [a, b] is considered a [Any]. When the element types in an array literal are entirely unrelated (Int? and String?), the array type is inferred to be [Any].

In the closure passed to compactMap, you returned $0, which is of type Any. This means that $0 can never be nil. All the optionals inside the array are all wrapped in an Any the moment you put them in the array. Because you never return a nil in the closure, all the elements stay in the result array.

The compiler can warn you about wrapping optionals in non-optional Anys:

var a: String? = "abc"

let any: Any = a // warning!

But unfortunately it doesn't warn you when you create arrays.

Anyway, you can get the expected behaviour by specifying that you want a [Any?]:

let result = ([a, b] as [Any?]).compactMap { $0 }

So you kind of unwrap them from Any.

Or:

let result = [a as Any?, b as Any?].compactMap { $0 }

Why can an optional type be wrapped inside an Any?

According to the docs (In the Type Casting for Any and AnyObject section):

Any can represent an instance of any type at all, including function types.

Thus, Optional<T> undoubtedly can be represented by Any.

Why can compactMap not remove nil values from an array of optional Any?

Firstly, change the type of arrayOfChildrenValues to [Any], so it is no longer optional, since \.value doesn't return an optional.

Now - you want to see if the element of type Any is an optional. To do this, you can try match it to Optional<Any>.none.

Change the following parts:

let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.filter { element in
switch element {
case Optional<Any>.none: return false
default: return true
}
}

Other ways

Here is another way:

let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = (arrayOfChildrenValues as [Any?]).compactMap { $0 }

However, there is another method with some strange behaviour happening, I'm not sure I can fully understand.

Here's the code:

let arrayOfChildrenValues: [Any?] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.compactMap { $0 }

The reason why this is strange is because all the other methods work with arrayOfChildrenValues being expressed like { element in element.value }, however this only works when using key-paths. Perhaps it's because the return type is inferred as [Any?], so it does a cast similar to the example above?

Why does your other example work?

The reason why compactMap doesn't work on [Any] is because a value of type Any cannot be not nil, since it is not an optional.

You can test this, by trying compactMap on [Any] instead of [Any?]. Modify your other example, as so:

let anyArray: [Any] = array.map { $0 as Any }
print(anyArray.compactMap({ element in element }))

Notice this prints [nil, Optional("Hello, world!"), nil] - and has also wrapped all our values in an optional because in reality this is still of type [Any?], 'disguised' as [Any].

CompactMap to filter objects that have nil properties

You can still use Array.compactMap

With if-statement

anotherObject.array.compactMap { object in
if let property = object.property {
return Object(property: property)
}
return nil
}

With guard statement

anotherObject.array.compactMap {
guard let property = $0.property else { return nil }

return Object(property: property)
}

ternary operator example

anotherObject.array.compactMap { object in
object.property == nil ? nil : Object(property: object.property!)
}

Swift Compact Map returning empty

In your DummyUser model you are using failable initializer, so in case of wrong dictionary provided to init method it will return nil.

compactMap automatically automatically filters nil's and that's the reason why your output is empty.

Looking at this piece of code:

let names = objects.map { $0["name"]!}
return objects.compactMap(DummyUser.init)

I would debug this variable called names because it probably has wrong input for the DummyUser initializer. It should be dictionary containing all of your DummyUser parameters. You can also debug your failable initializer to see which of the parameter is missing.

Swift - if string is nil. don't add it to the array

You can try ( Swift 4 )

let arr = [img1,img2].compactMap{$0}.map{AlamofireSource(urlString:$0)!}

or

let arr = alamofireSource.compactMap{$0}

for Swift 3

let arr = alamofireSource.flatMap{$0}

compactMap behaves differently when storing in an optional variable

I submitted this behavior as a bug at bugs.swift.org, and it came back as "works as intended." I had to give the response some thought in order to find a way to explain it to you; I think this re-expresses it pretty accurately and clearly. Here we go!

To see what's going on here, let's write something like compactMap ourselves. Pretend that compactMap does three things:

  1. Maps the original array through the given transform, which is expected to produce Optionals; in this particular example, it produces Int? elements.

  2. Filters out nils.

  3. Force unwraps the Optionals (safe because there are now no nils).

So here's the "normal" behavior, decomposed into this way of understanding it:

let marks = ["86", "45", "thiry six", "76"]
let result = marks.map { element -> Int? in
return Int(element)
}.filter { element in
return element != nil
}.map { element in
return element!
}

Okay, but in your example, the cast to [Int?] tells compactMap to output Int?, which means that its first map must produce Int??.

let result3 = marks.map { element -> Int?? in
return Int(element) // wrapped in extra Optional!
}.filter { element in
return element != nil
}.map { element in
return element!
}

So the first map produces double-wrapped Optionals, namely Optional(Optional(86)), Optional(Optional(45)), Optional(nil), Optional(Optional(76)).

None of those is nil, so they all pass thru the filter, and then they are all unwrapped once to give the result you're printing out.

The Swift expert who responded to my report admitted that there is something counterintuitive about this, but it's the price we pay for the automatic behavior where assigning into an Optional performs automatic wrapping. In other words, you can say

let i : Int? = 1

because 1 is wrapped in an Optional for you on the way into the assignment. Your [Int?] cast asks for the very same sort of behavior.

The workaround is to specify the transform's output type yourself, explicitly:

let result3 = marks.compactMap {element -> Int? in Int(element) }

That prevents the compiler from drawing its own conclusions about what the output type of the map function should be. Problem solved.

[You might also want to look at the WWDC 2020 video on type inference in Swift.]

Can I give back nil instead of a tuple?

Did you try this,

... { q -> (String?, GetUserQuestionsOut?) in

}

or

 ... { q -> (String, GetUserQuestionsOut)? in

}

on the other hand, maybe you could consider using struct(model) or typealias instead of tuple

Issue in understanding compactMap & flatMap

compactMap should be used to filter out nil elements from an array of Optionals, while flatMap can be used to flatten out a multi-dimensional array. However, you need to do both.

let final = myArray.flatMap{$0.compactMap{$0}}

print("Result:\(final)")

compactMap result is of weird type

You actually need flatMap, not compactMap.

Even though previously (before Swift 4.1), compactMap was also called flatMap, it had a different implementation and function signature than the current flatMap, since compactMap can be used instead of consecutive map and filter calls to map each element to a new element while only keeping non-nil elements. On the other hand, flatMap flattens out nested lists while mapping elements.

This is the still existing flatMap on Sequence, while this is the deprecated flatMap on Sequence that was renamed to compactMap. As you can see, the function signature of the renamed version was

func flatMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

, so its closure input argument returned an Optional value (just like compactMap does now), while the still existing flatMap has a function signature

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

, which doesn't return an Optional in its closure.

You should use the non-deprecated flatMap to flatten out your nested Array<Array<Member>>.

let allMembers = teams.flatMap { $0.members }

Two Returns with no condition in Swift

There is the first return, return elems.compactMap, that returns the result of the compactMap as the function result (array) then there are two returns inside the closure that returns the result of the mapping i.e what gets added to the array.

If the given value is larger than nums.count / 3 then key is returned and added to the array otherwise nil is returned meaning nothing gets added to the array.



Related Topics



Leave a reply



Submit