compactMap behaves differently when storing in an optional variable - swift

Consider the following array.
let marks = ["86", "45", "thiry six", "76"]
I've explained my doubts in the following two cases.
Case#1
// Compact map without optionals
let compactMapped: [Int] = marks.compactMap { Int($0) }
print("\(compactMapped)")
Result - [86, 45, 76]
Case#2
// Compact map with optionals
let compactMappedOptional: [Int?] = marks.compactMap { Int($0) }
print("\(compactMappedOptional)")
Result - [Optional(86), Optional(45), nil, Optional(76)]
Why there is "nil" in the result of Case#2? Can anyone explain why is it not like this [Optional(86), Optional(45), Optional(76)] in Case#2? (PFA playground)

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:
Maps the original array through the given transform, which is expected to produce Optionals; in this particular example, it produces Int? elements.
Filters out nils.
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.]

Related

Check if string contains optional string in Swift, but only if it's not nil

I'd like to add an optional "filter" parameter to a function that processes a list of strings. If the filter is nil, I'd like to process all the strings, otherwise I'd only like to process the ones that contain the filter. Roughly:
func process(items: [String], filter: String?) {
for item in items {
if filter == nil || item.contains(filter) {
// Do something with item
}
}
}
The typechecker complains about passing filter into contains since it's optional, but contains takes a String. I could of course force-unwrap, but that seems ugly. The above would compile in Kotlin because it would smart-cast away the optional, but what's the idiomatic way to express this in Swift?
Thanks!
There is a map on Optional that executes the provided closure only if the optional is not nil. You can use map along with the nil coalescing operator ?? to provide the default value of true if the map returns nil because the filter is nil:
func process(items: [String], filter: String?) {
for item in items {
if filter.map(item.contains) ?? true {
// process item
}
}
}
The key here is (and always was going to be) the nil-coalescing operator. This lets you unwrap an Optional safely if it is not nil, but if it is nil, you substitute another value.
The issue with the question, however, is that it poses itself in an unSwifty way. You would never write a filtering function that takes an Optional String. You would write a filtering function that takes an Optional filtering function! Instead of limiting the caller to contains and a String, you want to allow any type of array and any filtering function. That is the Swifty way to write this function.
And while we are at it, the function should not predetermine what is done with the members of the array either. That should be another function that the caller passes in!
Thus we end up with this far more general and much Swiftier statement of the goal:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
// ...
}
Okay! So let's write this function. The processing part is easy; this is the predicate to a forEach call. Like this:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
arr.forEach(processPred)
}
Very good! But we have neglected to deal with the filtering function. Let's take care of that. Clearly, this is where the rubber meets the road. And just as clearly, it is evident that this is a function to be passed into filter. But we can only do that by unwrapping if it is not nil. What if it is nil? The OP has specified that in that case we should let all element through the filter.
So consider a predicate function that is to be passed into filter. What is the function that lets everything through? It is:
{ _ in true }
So that is what to put after the nil-coalescing operator! Therefore, this is the full answer:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
arr.filter(filterPred ?? { _ in true }).forEach(processPred)
}
And here are two tests:
let arr = [1,2,3,4]
self.process(arr) { print($0) }
self.process(arr) { $0 % 2 == 0 } processing: { print($0) }

Optional chaining with Swift strings

With optional chaining, if I have a Swift variable
var s: String?
s might contain nil, or a String wrapped in an Optional. So, I tried this to get its length:
let count = s?.characters?.count ?? 0
However, the compiler wants this:
let count = s?.characters.count ?? 0
My understanding of optional chaining is that, once you start using ?. in a dotted expression, the rest of the properties are made optional and are typically accessed by ?., not ..
So, I dug a little further and tried this in the playground:
var s: String? = "Foo"
print(s?.characters)
// Output: Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: 0x00000001145e893f, _countAndFlags: 3, _owner: nil)))
The result indicates that s?.characters is indeed an Optional instance, indicating that s?.characters.count should be illegal.
Can someone help me understand this state of affairs?
When you say:
My understanding of optional chaining is that, once you start using ?. in a dotted expression, the rest of the properties are made optional and are typically accessed by ?., not ..
I would say that you are almost there.
It’s not that all the properties are made optional, it’s that the original call is optional, so it looks like the other properties are optional.
characters is not an optional property, and neither is count, but the value that you are calling it on is optional. If there is a value, then the characters and count properties will return a value; otherwise, nil is returned. It is because of this that the result of s?.characters.count returns an Int?.
If either of the properties were optional, then you would need to add ? to it, but, in your case, they aren’t. So you don’t.
Edited following comment
From the comment:
I still find it strange that both s?.characters.count and (s?.characters)?.count compile, but (s?.characters).count doesn't. Why is there a difference between the first and the last expression?
I’ll try and answer it here, where there is more room than in the comment field:
s?.characters.count
If s is nil, the whole expression returns nil, otherwise an Int. So the return type is Int?.
(s?.characters).count // Won’t compile
Breaking this down: if s is nil, then (s?.characters) is nil, so we can’t call count on it.
In order to call the count property on (s?.characters), the expression needs to be optionally unwrapped, i.e. written as:
(s?.characters)?.count
Edited to add further
The best I can get to explaining this is with this bit of playground code:
let s: String? = "hello"
s?.characters.count
(s?.characters)?.count
(s)?.characters.count
((s)?.characters)?.count
// s?.characters.count
func method1(s: String?) -> Int? {
guard let s = s else { return nil }
return s.characters.count
}
// (s?.characters).count
func method2(s: String?) -> Int? {
guard let c = s?.characters else { return nil }
return c.count
}
method1(s)
method2(s)
On the Swift-users mailing list, Ingo Maier was kind enough to point me to the section on optional chaining expressions in the Swift language spec, which states:
If a postfix expression that contains an optional-chaining expression is nested inside other postfix expressions, only the outermost expression returns an optional type.
It continues with the example:
var c: SomeClass?
var result: Bool? = c?.property.performAction()
This explains why the compiler wants s?.characters.count in my example above, and I consider that it answers the original question. However, as #Martin R observed in a comment, there is still a mystery as to why these two expressions are treated differently by the compiler:
s?.characters.count
(s?.characters).count
If I am reading the spec properly, the subexpression
(s?.characters)
is "nested inside" the overall postfix expression
(s?.characters).count
and thus should be treated the same as the non-parenthesized version. But that's a separate issue.
Thanks to all for the contributions!

When filtering an array literal in swift, why does the result contain optionals?

A contrived example to be sure, but why is the result an array of optionals?
let r = [1,2,3].filter { sourceElement in
return !["1", "2"].contains { removeElement in
sourceElement == Int(removeElement)
}
}
print(r.dynamicType)
Either type casting the source array or assigning it to a variable returns an array of Ints.
let seq = [1,2,3]
let r2 = seq.filter { sourceElement in
return !["1", "2"].contains { removeElement in
sourceElement == Int(removeElement)
}
}
print(r2.dynamicType) // "Array<Int>\n"
Shouldn't both results be of the same type?
I don’t think it’s necessarily a bug though it is confusing. It’s a question of where the promotion to optional happens to make the whole statement compile. A shorter repro that has the same behavior would be:
let i: Int? = 1
// x will be [Int?]
let x = [1,2,3].filter { $0 == i }
Bear in mind when you write nonOptional == someOptional the type of the lhs must be promoted to optional implicitly in order for it to work, because the == that you are using is this one in which both sides must be optional:
public func ==<T>(lhs: T?, rhs: T?) -> Bool
The compiler needs to promote something in this entire statement to be an optional, and what it chose was the integer literals inside [1,2,3]. You were instead expecting the promotion to happen at the point of the ==, so you could compare the non-optional sourceElement with the optional result of Int(_:String), but this isn’t necessarily guaranteed (not sure to what extent the ordering/precedence of these promotions is specced vs just the way the compiler was coded…)
The reason this doesn’t happen in the two-line version is when you write as one line let seq = [1,2,3], the type of seq is decided there. Then on the next line, the compiler doesn’t have as much latitude, therefore it must promote sourceElement to be an Int? so it can be compared with Int(removeElement) using ==.
Another way of making the code perform the conversion at the point you expect would be:
let r = [1,2,3].filter { sourceElement in
return !["1", "2"].contains { (removeElement: String)->Bool in
// force the optional upgrade to happen here rather than
// on the [1,2,3] literal...
Optional(sourceElement) == Int(removeElement)
}
}

Array of String printing Optional, why?

I am playing with Arrays in playground and I am bit confused. Here is code:
var players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
print(players.isEmpty) // False
var currentPlayer = players.first // "tob"
print(currentPlayer) // "Optional("tob")\n"
Why does it says "Optional"?
I found explanation: "The property first actually returns an optional, because if the array were empty, first would return nil."
But it is not empty. .isEmpty //false, So I am not understanding this.
Thanks for help in advance.
The correct way to think of Optional is that this may or may not have a value. What is the first element of an empty list? There is no such thing. It is not a value. We call that lack of a value nil or .None.
In Swift a variable must have a specific type. So your example:
let currentPlayer = players.first
What is the type of currentPlayer? It may be a String, or it may be nothing at all. It is a "maybe string" and in Swift that's called an Optional<String>. Whether players has elements or doesn't have elements doesn't change the type of currentPlayer.
If you want to do something if-and-only-if the variable has a value, then there are many ways. The simplest is if-let.
let players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
print(players.isEmpty) // False
if let currentPlayer = players.first {
print(currentPlayer)
}
This will print tob as you're expecting.
Another very common approach is the guard let
let players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
guard let currentPlayer = players.first else { return }
print(currentPlayer)
This lets you avoid nesting the rest of your function inside of curly braces, but otherwise is the same approach.
It is possible to convert an Optional into its underlying type using !, but this is very dangerous and should be avoided except where absolutely necessary. Tools like if-let and guard-let (and also Optional.map) are almost always preferred.
But the key here is to understand that all Swift variables have a single type, and sometimes that type is "maybe it has a value, maybe it doesn't."
If we look at the description of first, we will see that it always returns optional type:
public var first: Self.Generator.Element? { get }

How to handle initial nil value for reduce functions

I would like to learn and use more functional programming in Swift. So, I've been trying various things in playground. I don't understand Reduce, though. The basic textbook examples work, but I can't get my head around this problem.
I have an array of strings called "toDoItems". I would like to get the longest string in this array. What is the best practice for handling the initial nil value in such cases? I think this probably happens often. I thought of writing a custom function and use it.
func optionalMax(maxSofar: Int?, newElement: Int) -> Int {
if let definiteMaxSofar = maxSofar {
return max(definiteMaxSofar, newElement)
}
return newElement
}
// Just testing - nums is an array of Ints. Works.
var maxValueOfInts = nums.reduce(0) { optionalMax($0, $1) }
// ERROR: cannot invoke 'reduce' with an argument list of type ‘(nil, (_,_)->_)'
var longestOfStrings = toDoItems.reduce(nil) { optionalMax(count($0), count($1)) }
It might just be that Swift does not automatically infer the type of your initial value. Try making it clear by explicitly declaring it:
var longestOfStrings = toDoItems.reduce(nil as Int?) { optionalMax($0, count($1)) }
By the way notice that I do not count on $0 (your accumulator) since it is not a String but an optional Int Int?
Generally to avoid confusion reading the code later, I explicitly label the accumulator as a and the element coming in from the serie as x:
var longestOfStrings = toDoItems.reduce(nil as Int?) { a, x in optionalMax(a, count(x)) }
This way should be clearer than $0 and $1 in code when the accumulator or the single element are used.
Hope this helps
Initialise it with an empty string "" rather than nil. Or you could even initialise it with the first element of the array, but an empty string seems better.
Second go at this after writing some wrong code, this will return the longest string if you are happy with an empty string being returned for an empty array:
toDoItems.reduce("") { count($0) > count($1) ? $0 : $1 }
Or if you want nil, use
toDoItems.reduce(nil as String?) { count($0!) > count($1) ? $0 : $1 }
The problem is that the compiler cannot infer the types you are using for your seed and accumulator closure if you seed with nil, and you also need to get the optional type correct when using the optional string as $0.