Strange optional resolution. Knowing the Swift philosophy I would expect a within if block to be non optional.
let a: String? = "a"
let b: String? = "b"
if let a = [a,b].first(where: {$0 == "a" }) {
print("\(a)") // a is optional
}
Strange optional resolution. Knowing the Swift philosophy I would expect a within if block to be non optional.
let a: String? = "a"
let b: String? = "b"
if let a = [a,b].first(where: {$0 == "a" }) {
print("\(a)") // a is optional
}
.first()
itself returns an optional (String??
). So your if let a = ...
only binds the wrapped value of that first optional. The collection itself still has optional elements.
Then you won't be able getting nil in this example (which may be desired):
if let x = [nil as String?, "b" as String?].first(where: { _ in true }) {
print("\(x)") // nil
}
What's less than obvious here is that your "a"
is being promoted to Optional<String>
, triggering this overload:
If you want the overload that doesn't rely on optionals, I suggest compacted
.
if let a = [a, b].compacted().first(where: { $0 == "a" }) {
@anon9791410 I understand about compacted()
but imho the original example yet it is breaking the philosophy as what is the point of even having that expression in if
.
@tera I havent played with it for a long time. But I remember that in one of the swift evolutions there were quite an issue with double optionals and it was resolved in a way that I described. So I was surprised to run into them again.
I am surprised, too. I was under the impression that optional binding removed an arbitrary number of levels of optionality, such that both
let x: Int? = 0
if let unwrapped = x {
print(type(of: unwrapped))
}
and
let y: Int??? = 0
if let unwrapped = y {
print(type(of: unwrapped))
}
should both print Int
, but I'm apparently misremembering. (Because when I did this in a Playground the second one sure printed Optional<Optional<Int>>
.)
This is an interesting note. I found this post:
So in your example you could do the optional binding as if let a = [a,b].first(where: {$0 == "a" }) as? String {
to get a non-optional String
out of it.
The problem is not that I dont know how to avoid it. The problem is that it creates some kind of witchcraft in a place you dont expect it imho. if let
for Multiple Optionals to get rid of just one optional has no sense. It is more philosophical question.
if let first = [nil, "a"].first
has no sense to proceed to the if block
This reply in another thread goes into a great explanation of why the distinction is important:
The other provides some rationale for it.
When you put optionals in an array, the array is elevated to the highest level present. E.g.
let a: String?? = "a"
let b: String?????????? = "b"
[a, b] // is [String??????????], not [String??]
first
adds one more ?
, and if let
takes one away.
if let a {
a // String?
}
if case let a?? = a {
a // String
}
You have this idea of "Swift philosophy" not matching that; that is only your current interpretation, and you can deprogram it.
Why would you have written the example code in the first place, though? There's not a reason to extract a match, when you have a non-optional version of the match to begin with.
let match = "a"
if [a, b].contains(match) {
print("\(match)")
}
@anon9791410 You are right in that I interpret it myself and call it a philosophy. I might be wrong but i think in swift 3 conversion to 4 there was an attempt to get rid of optionals of optionals. But it could also be that my memory is failing me.
Also, poptentially I do not understand what other wrote me.
I personally find that let first = [nil, "a"].first
by itself has absolute sense.
but
if let first = [nil, "a"].first {
// Should not appear here
}
this how I (and personally I) would expect it to work.
Sorry, my bad.
I think you're thinking of SE- 0230 Flatten nested optionals resulting from 'try?'.
There is no general direction of trying to reduce the occurrences of optional optionals. The compositional nature of Optional is an advantage over the conventional notion of null
as single, flat value. It's a feature, not a bug.
In this Array example, it's less obvious what would be preferable, but I think this dictionary example makes a very strong case for why "it should only unwrap one layer of optionality" is the right way to go:
let d: [String: Int?] = ["key": nil, "other key": 123]
if let existingValue = d["key"] {
print(#"The dictionary contains the value "\#(existingValue as Any)" for the key "key"#)
} else {
print(#"The dictionary doesn't contain any values for the key "key"#)
}
You would want to hit the if
case, not the else
case, because the dictionary does contain a value for "key"
, which is nil
. This is semantically distinct from having no value at all.
Consider C# by comparison. Their null
is just one single flat value. There's no way to encode the difference between:
null
because the value is not there"null
because the value is there and is actually null
"Checking value == null
is not enough, because it can't those two cases apart, so their TryGetValue
API needs to return a separate bool
:
public bool TryGetValue (TKey key, out TValue value);
You may need something like this:
protocol OptionalProtocol {
static var wrappedType: Any.Type { get }
// nested optional types unwrapping
static var fullUnwrappedType: Any.Type { get }
var isNil : Bool { get }
var wrappedValue : Any { get }
}
extension Optional : OptionalProtocol {
static var wrappedType: Any.Type {
Wrapped.self
}
static var fullUnwrappedType: Any.Type {
var currentType = wrappedType
while let actual = currentType as? OptionalProtocol.Type {
currentType = actual.wrappedType
}
return currentType
}
var isNil: Bool { self == nil }
var wrappedValue: Any { self! }
}
extension Optional where Wrapped == Any {
// nested optionals unwrapping
init( fullUnwrapping value:Any ) {
var currentValue = value
while let optionalValue = currentValue as? OptionalProtocol {
if optionalValue.isNil {
self = .none
return
} else {
currentValue = optionalValue.wrappedValue
}
}
self = .some( currentValue )
}
}
let y: Int??????????????????????? = 0
if let unwrapped = Optional(fullUnwrapping: y as Any) {
print( type(of: unwrapped) ) // print Int
}
let unwrappedType = type(of: y).fullUnwrappedType
print( type(of: unwrappedType) ) // print Int.Type