Apologies for coming so late to this pitch.
I have some misgivings about adding this feature for a few reasons:
I'm uneasy about breaking the seal on adding a method on all types – this would be a big step in policy terms, and I think it should not be taken lightly. The first use case for it should be extremely compelling, and I am not sure this is compelling enough.
The use case for this with existing values makes me particularly uneasy. I find:
originalComponents.with { components in
components.port? += 1
}
as a spelling for "create a copy of originalComponents
, but with this value changed" very strange. This may be a matter of spelling – with
is not at all a good name for this "copy and tweak" operation. Maybe a different name could be better – but I'm not sure a better one is readily available. And a different more verbose one would detract from the appeal with other use cases.
I definitely see the need for this feature in particular for initializing variables from an expression, especially global variables. It's definitely a gap, currently filled by immediately-executed closures. But I would prefer another path to solve this.
@hamishknight and I have been working on a proposal for syntax that would allow multi-statement if
and switch
branches to be expressions, through the introduction of a then
keyword (draft implementation here):
let url =
if let hostname {
var components = URLComponents()
components.scheme = "https"
components.host = hostname
components.path = "/c/evolution/18"
then components.url
} else {
log("no URL supplied, falling back to default")
then defaultUrl
}
This addresses one of the main future directions left by SE-0380.
Once this is done, another future direction becomes very appealing: do
expressions:
let url =
do {
try fetchUrl()
} catch {
log("something went wrong")
then defaultUrl
}
If do
statements become expressions, then such expressions (with or without catch
clauses) could also become the natural alternative to the immediately-executed-closure idiom, the main target motivation of this pitch.
Finally, I have a general wariness of implementing control flow using closures. The most important place where this was a mistake was the introduction of forEach
, which is highly problematic due to its changing the meaning of return
and continue
versus it's for...in
equivalent. This proposal doesn't have quite the same challenges, but in general if given a choice I would rather model things like this with real language control flow like a do
block (if such a thing ends up being an expression anyway) than duplicating that functionality a second time with a library function (especially, as is the case here, a library function you cannot write without special accommodation from the compiler).