New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-13107] compactMap behavior changes depending on receiving type? #55553
Comments
The problem is that the type parameters are not getting instantiated the way you are expecting them to be. The signature for func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] The second call is let result3 : [Int?] = marks.compactMap { Int($0) } Based on the explicit type annotation, the compiler picks Since I understand this is probably not a very satisfactory answer. As you pointed out, this breaks what is seemingly a basic reasoning principle: let-substitution should not change the semantics of the program. Unfortunately, the presence of convenience features like implicit conversions, the lack of a formalized type system (on which one can prove theorems, such as obeying let-substitution) and the complexity of different interacting language features make it quite difficult to guarantee properties like this. |
Thanks theindigamer (JIRA User), the Int?? explanation was in fact my other secret hypothesis about this. This is hard for the average user to reason about, but as you say, it’s the price we pay for the convenience of magic Optional wrapping on assignment. |
theindigamer (JIRA User) I've attempted to re-explain this for the layman at https://stackoverflow.com/a/62642823/341994. I hope I have not misrepresented your explanation. Thanks again! |
Thanks. I would not call myself a Swift expert but otherwise your explanation is pretty spot-on. 🙂 If I were to make an adjustment, I would make the explanation in terms of
|
Another "interesting" example, where a simple refactoring breaks code. You'd think that I should be able to refactor // Before
func f(_: Int8) {}
f(0) // Ok
// After
func f(_: Int8) {}
let x = 0
f(x) // error: cannot convert value of type 'Int' to expected argument type 'Int8' This is because inference works one statement at a time, and defaults are immediately "collapsed". Hence |
Environment
Xcode 12, but the behavior is the same in, say, Xcode 9.2 (where of course `compactMap` is called `flatMap`)
Additional Detail from JIRA
md5: 5405a15a45ffe8222241a56c6a24ba15
Issue Description:
The following has just arisen on SO:
Write a project or playground that calls `f()`. I would expect result2 and result3 to be the same; the first uses `result` as an intermediary, the second simply assigns directly what would have been assigned to the intermediary. However, they are different. `result3` has a `nil` element that I can't explain, since `compactMap` has the job of eliminating nil.
The `result3` line is in fact behaving as if we had called `map` and not `compactMap`, but I cannot explain why merely casting would cause that to happen.
It looks like this behavior is very old in Swift, so perhaps it's by design? But if so, it's a very odd design; this is a circumstance where
gives a different result from
which is not what one usually expects of a computer language.
The text was updated successfully, but these errors were encountered: