I think perhaps the confusion, and strong opposition, here is due to the earlier half of the thread proposing that the optional sequence is implicitly skipped if nil, which I wasn't in favour of at the time. The latter half of the thread seems to have settled on having some sort of syntactic marker, which should make it acceptable to more people. I'm personally not sure what the particular syntactic marker should be, though, out of the 3 or so that have been floated.
Personally, I like for x in xs?
since it feels like the existing optional pattern matching syntax.
I favor for let x = xOpt ...
, because it matches the binding within an if
statement. I think newcomers would find it more logical as well.
I have to admit, though, that most times you don't need any access to the bound variable within a loop, which stands in contradistinction to an if
statement.
I‘m not sure if I understand this right - can you extend the example?
I would expect that x is a Sequence, but what would be the name of the element inside the loop?
I haven't thought it through. I was merely echoing what Svein Halvor Halvorsen wrote above. If the pitch moves forward, I am sure there will be plenty of ideas. To bring it back to the conceptual stage, I would say that we want to combine the following into one construct:
if let seq = optionalSeq {
for s in seq {
}
}
Perhaps for s in let seq = optionalSequence { ... }
.
No, it is a bad idea. nil
means "no value" not the empty or identity value.
Again, doesn't ExpressibleByNilLiteral
already do exactly this?
This is what the documentation says
So Apple's official position is "don't do that".
nil
has a specific meaning in Swift—the absence of a value. [...]ExpressibleByNilLiteral
conformance for types that usenil
for other purposes is discouraged.
We're discussing using it for marking the canonical absence of a value for a type that naturally represents that absence. The purely declarative statement that only Optional
conforms to it doesn't change that.
I was responding to your question " doesn't ExpressibleByNilLiteral
already do exactly this?" The answer is no, it doesn't already do this and I cited Apple's documentation to show that it doesn't already do this, at least not in the Standard Library, and they'd rather you didn't adopt it for your own types.
This proposal has been scheduled for review starting this wednesday as SE-0231.
I think that more natural would be for element in? elements
similar to value as? SomeType
I have been listening to the back catalogue of the terrific Swift Over Coffee podcast over the past few days where on one episode they mentioned this SE proposal. They did mention for-in?
and seemed in most favour of for?
but neither of those sat right with me. It took me a few hours before I realised for-in-let-else
was, IMO, the correct way of iterating over optionals.
I was delighted to see @Avi had already proposed this option but I’d like to take it further.
Instead of the existing modus operandi:
let maybeArray: [Int]? = [1, 2, 3, 4, 5]
if let defeinitelyArray = maybeArray {
for element in definitelyArray {
// maybeArray continues to be [Int]?
// definitelyArray is [Int] here
// element is Int here
}
}
As @Avi suggests, using for-in-let
would look like the following:
let maybeArray: [Int]? = [1, 2, 3, 4, 5]
for element in let definitelyArray = maybeArray {
// maybeArray still accessible as [Int]?
// definitelyArray is [Int] here
// element is Int here
}
Taking it further one step, I propose a for-in-let-else
construct, which of course, looks like this:
let maybeArray: [Int]? = [1, 2, 3, 4, 5]
for element in let definitelyArray = maybeArray {
// same as the prior example
} else {
// unwrapping failed because maybeArray was nil
}
This would also work with underscore assignment, as follows:
let maybeArray: [Int]? = [1, 2, 3, 4, 5]
for element in _ = maybeArray {
// maybeArray still accessible as [Int]?
// Careful, unwrapped version of the array is not available here.
// element is Int here
} else {
// unwrapping failed because maybeArray was nil
}
Working with optional arrays of optional elements:
let maybeArray: [Int?]? = [1, 2, nil, nil, 5]
for element in let definitelyArray = maybeArray {
// maybeArray still accessible as [Int]?
// definitelyArray is [Int] here
// Careful, element is Int? this time
} else {
// unwrapping failed because maybeArray was nil
}
Dealing with multiple let statements:
let maybeMaybeArray: [Int]?? = [1, 2, 3, 4, 5]
for element in let definitelyMaybeArray = maybeMaybeArray, let definitelyArray = definitelyMaybeArray {
// maybeMaybeArray is [Int]?? here
// definitelyMaybeArray is [Int]? here
// definitelyArray is [Int] here
// element is Int here
} else {
// unwrapping failed because maybeMaybeArray or maybeArray was nil
}
In this case, the for loop iterates over the last Sequence
assigned.
As perfect a solution as I wish this were, I can think of two cases of ambiguity that arise with this solution. The first arises while dealing with where
clauses, as follows:
let maybeArray: [Int]? = [1, 2, 3, 4, 5]
for element in let definitelyArray = maybeArray where element.isMultiple(of: 2) {
// maybeArray still accessible as [Int]?
// definitelyArray is [Int] here
// element is an even Int here
} else {
// Are we here because unwrapping failed because maybeArray was nil?
// Or because !element.isMultiple(of: 2)?
}
I think the else clause should be entered when the unwrapping failed, not when the where clause wasn't met, but YMMV.
The second, if optional elements of an optional array are given the chance to be unwrapped before entering the loop, as follows:
let maybeArray: [Int?]? = [1, 2, nil, nil, 5]
for maybeElement, let definitelyElement = maybeElement in let definitelyArray = maybeArray {
// maybeMaybeArray still accessible as [Int]?
// definitelyArray is [Int] here
// maybeElement is Int? this time
// definitelyElement is nicely Int though
} else {
// Are we here because unwrapping failed because maybeArray was nil?
// Or because unwrapping failed because maybeElement was nil
}
Of course the two ambiguities above can be coalesced into a monster of a for-let-in-let-where-else
statement
let maybeMaybeArray: [Int?]?? = [1, 2, nil, nil, 5]
for maybeElement, let definitelyElement = maybeElement in let definitelyMaybeArray = maybeMaybeArray, let definitelyArray = definitelyMaybeArray where definitelyElement.isMultiple(of: 2) && element < array.count / 2 {
// maybeArray still accessible as [Int]?
// definitelyArray is [Int] here
// maybeElement is an Int?
// definitelyElement is the unwrapped value of maybeElemet. It also satisfies the where clause.
} else {
// Are we here because unwrapping failed because maybeArray was nil?
// Or because unwrapping failed because maybeElement was nil
// Or because !definitelyElement.isMultiple(of: 2)?
}
which I would rewrite as:
for
maybeElement,
let definitelyElement = maybeElement
in
let definitelyMaybeArray = maybeMaybeArray,
let definitelyArray = definitelyMaybeArray
where
definitelyElement.isMultiple(of: 2) &&
element < array.count / 2
{
⋮
} else {
⋮
}
In conclusion, I understand this proposal has been rejected months ago, however, this was a fun thought experiment to mull. I do hope that my suggestion rekindles this discussion but I realise it might just be wishful thinking. In any case I'd love to hear critique of my proposed solution.