Short answer: math. 
Longer answer: if let is just language sugar, and it’s completely fine to only use it for Optional. But there is no if let for Result, Array or Publisher. Working the other way around—without special cases—once you recognise map and flatMap as the generic interface for “transform inside a context” and “chain contexts without nesting,” you use the same vocabulary for Optional, Array, Result, Combine, etc.
IMHO Swift is close to Scala, Rust, F#, OCaml. It’s functional under the hood, so having FP primitives makes sense.
Another point having those functions is you can actually chain them:
Int("42")
.map { $0 + 1 }
.flatMap { x -> Int? in
let (result, overflowed) = x.multipliedReportingOverflow(by: x)
return overflowed ? nil : result
}
.flatMap { print($0) }
In if let case more checks you add then less readable it becomes.
Btw a bit off topic now, but think these examples are not quite equivalent, and it's good to figure out why to understand whole FP point: printing is a side effect, e.g. easy to see if you put some async function there.
Something similar would be:
let nonOverflowingSquare = Int("42")
.map { $0 + 1 }
.flatMap { x -> Int? in
let (result, overflowed) = x.multipliedReportingOverflow(by: x)
return overflowed ? nil : result
}
// Can't do: .flatMap { await printAndSend($0) }
// Explicit side effect
if let nonOverflowingSquare {
async printAndSend(nonOverflowingSquare)
}
And another example should be:
let possibleNumber: Int? = Int("42")
var nonOverflowingSquare: Int?
if var number = possibleNumber {
number += 1
let (result, overflowed) = number.multipliedReportingOverflow(by: number)
nonOverflowingSquare = overflowed ? nil : result
// putting async function will couple everything here and hard to read at least
}
// better do separately for maintenance and readability
if let nonOverflowingSquare {
await printAndSend(nonOverflowingSquare)
// Prints and sends "1849"
}
Two things:
- Adding
var to number introduces mutation, which can lead to bugs (another side effect).
- Swift does have simple effect system with
async and throws, and by using them you can look how it starts to punish you.
I think that's one of the reasons people love talking about side effects in FP languages—because tracking them at compile time helps you write more maintainable, readable code.
Overall, which paradigms to include in a language and why is a complex topic, but understanding how to use these constructions is crucial to understand why they are in the language in the first place.