struct NC: ~Copyable {
var x: Int
consuming func foo() -> Int {
x
}
}
func testA() {
let nc = NC(x: 1) // OK
let y = nc.foo() + 1
print(y)
}
func testB() {
let nc = NC(x: 1) // Noncopyable 'nc' cannot be consumed when captured by an escaping closure
let y = {
nc.foo() + 1
}()
print(y)
}
func testC() {
var nc = NC(x: 1) // Error: Missing reinitialization of closure capture 'nc' after consume
let y = {
nc.foo() + 1
}()
print(y)
}
func testD() {
var nc = NC(x: 1) // OK
let y = { (nc: consuming NC) in
nc.foo() + 1
}(consume nc)
print(y)
}
Ah, I see - thanks, that's helpful and clear.
I was assuming move-only types would be implicitly moved into a closure in cases like this. It sounds like they can be moved, but only explicitly? That does indeed add some boilerplate to that use.
Tangentially, in testC
, though, why is there a problem? nc
isn't used outside nor beyond the closure, so why would (or should?) the compiler complain about it not being reinitialised? I'm assuming from context that the compiler does implicitly move it into the closure when it's a var
, as opposed to a let
?
I wish I knew.
That's right. And I find it a little more convenient. Now you write either
let x = try? foo() ?? "fallback"
and you can't do anything with the error, or
let x: String
do {
x = try foo()
} catch {
print(String(describing: error))
x = "fallback"
}
and this isn't pretty, because of the loss of type inference.
But with the proposed change you could write
let x = try foo() catch {
print(String(describing: error))
"fallback"
}
I understand the confusion. But I'd like to point that Swift allows self
shadowin, but only for explicit self
:
class Bar {
let x = 42
}
class Foo {
let y = 24
func foo() {
let z = { [self = Bar()] in
self.x // Ok
x // Cannot find 'x' in scope
self.y // Value of type 'Bar' has no member 'y'
y // Ok
}()
}
}
Ok, I'm warming up to the idea of try/catch as an expression style option since it helps preserve type inference as shown by @dmt.
If the proposed try/catch syntax were used, it might be useful for rethrowing or erasing/embedding upstream errors.
var bar: String = do {
try fetchString() catch { throw InnerError.fetchOperationFailed }
} catch { // catch in the outer do
print("Today is not your day.")
""
}
I assume both of these would work assuming guard expressions are allowed in a future version of Swift.
var bar: String = do {
guard let result = try fetchString() catch {
throw InnerError.fetchOperationFailed
} else {
throw InnerError.noResult
}
} catch { // catch in the outer do
print("Today is not your day.")
""
}
var bar: String = do {
let result = try guard fetchString() != nil else {
throw InnerError.noResult
} catch {
throw InnerError.fetchOperationFailed
}
} catch { // catch in the outer do
print("Today is not your day.")
""
}
just a useful tip i discovered recently: you can also shadow self
by making it a parameter of a static operator func
. i find this makes it a lot easier to factor mutating methods into inout
operators like +=
.
I'm having some trouble in Swift these days when Control Flow blocks and expressions get the same name, telling apart when when I'm doing legit Control Flow and when I'm in a multi-line expression with an implicit(!!!) return.
The most recent was my confusion was with result builders that the "for" in that is really more of a foreach {}
So all of a sudden do and catch will be sometimes be expression and sometimes Control Flow?
I don't mind having both CF and expression but how do I know which I'm doing if they have the same name? Maybe a "doSet" and "catchSet" would be okay? But people seem to really want the same names for these very different creatures? That's the part I don't understand.
Is this just the way modern languages do things? Could someone point me at a paper or a youtube video or give me some search terms so I can educate myself on why everyone hates control flow programming and wants expressions to overload those terms? Is it just "functional programing" and I just need to do it? I get the value of no side effects... but then on the other hand we have people adding ~Copyable and borrow to the language too, which seem all about a return to side effects, really.
Honestly, I'm flexible and can learn so I defer to what the community wants but I'd love some resources so I can understand the debate better.
Ah, the ship seems to have sailed and I'm just going to have to write C++ libraries now
[Accepted with modifications] SE-0380: `if` and `switch` expressions (This is very much a joke. I will enjoy not writing return in my switch statements with everyone else!)
Nobody hates imperative style in Swift so these papers don't exist. There are opinionated libraries for Swift that might choose to more strongly embrace either functional or imperative styles, but the core language tries to be un-opinionated. I certainly prefer one or the other depending on what feels better for the task at hand.
There isn't a contest to make functional programming take over Swift or anything like that. Swift was designed to be multi-paradigm from the beginning. I think there is a the desire to use the same constructs across the different supported programming language styles to avoid eating keywords and to make it more familiar. Both styles are considered equally relevant. Rust is very similar to Swift in regard to treating both equally.
As far as what common practices will be, that is just going to be determined by how people use Swift in real life. You might find that there ends up being a lot of consensus to use a functional style in some cases and an imperative style in others, but that is always going to be in constant flux as it was meant to be. The only exception might be the few opinionated libraries that Apple releases (i.e. SwiftUI) that will be determined primarily by Apple.
Most of the time the goal for library building is to make them un-opinionated, but there are exceptions if you are in a domain that strongly prefers one or the other. I would certainly want to use imperative style if I were building an interpreter and functional style for a mathematical calculation. Something like list processing is in a middle ground where either might be better.
Most modern languages with advanced type systems have chosen to be multi-paradigm or functional since that is where these type systems originated. Even looking at most modern languages in general there is a push to functional style now that so many people have used JavaScript or TypeScript. Of recent languages Go might be the biggest outlier which ended up strongly imperative, but with a very limited type system.
As for the implicit return. It doesn't exist for Never/Void types, so you just need to pay attention to return types. It also should be used for expressions only, so multi-line implicit return should be unsupported or rare. As a strongly typed language, in virtually all cases the compiler should catch accidentally doing an unintended implicit return. In your own code, if you are using an imperative style I would always do an explicit return. Save implicit returns for code that is purely using expressions. The core team is deliberately adding expressions to block constructs slowly to make sure imperative style isn't affected because it is important too!
Thank you for your reply.
As I belatedly realized, the ship has sailed on overloading block names as expressions, so I apologize for derailing the conversion. Now that I realize if/switch are (edit: going to be) expressions, I withdraw calling a do
expression something more decorated. Consistency is more important.
I use control flow blocks while also using functional paradigms (eeeek? Apologies.) so I would gently push back on the implication that OVERLOADING the block names doesn't make programming harder / code more ambiguous. Its not about imperative vs functional styles exactly as much is about the existence of any flow control statements in a project that might be functional in general but not religiously. But as you said, and as the powers that be have said, that's just going to be the "way things are done". And I will be fine. I'll just have to learn to look for equal signs? Maybe I'll have XCode make them a bright color or something.
Just please please, no multi-line expressions with no return
/yield
or something. That makes it the hardest to parse if its an expression vs block when scanning the code. From SE-0380 it looks like those are for one liners and multiline hasn't been decided yet and its really just the for
in result builder that is a special (uniquely problematic to me) case, so this is my one ask
I will say from a personal experience when I started using Swift it felt like easy-gracious-beautiful C/C++ and now it kinda feels like... not that. It's becoming its own thing. I just haven't been following evolution stuff previously and am trying to get a feel what the philosophy is so I can not be so taken by surprise when things don't work the way I expect/change.
Well I don't think there will be confusion with implicit returns with the restrictions the core team has (or will as it is expanded) put in place. Also many of the examples in this thread are assuming implicit return from multiline expressions will be allowed which I'm almost positive there will never be consensus in the Swift community for that to ever happen and the return keyword can't be used to return from a block.
So I think what you might be most lamenting is an expression in the middle of imperative code which is already common in the Rust language.
// forgive the nonsense example...
for a in b {
var c: Int = a
// This is pretty much the main situation where imperative will mix with functional.
let d = if a > 1 { 1 } else { 2 }
c = c + d
}
Because of higher-order functions like map
, Swift programmers are already heavily mixing functional and imperative styles. This really just takes that one step further.
I am personally fine with it as long as the lines don't feel too blurred. I think it is still pretty clear there are boundaries. If you don't like it then avoid mixing styles in your own code. Not sure what else to say about that...
It's not really question of what I do in my own code, its a question of scanning other peoples mixed paradigm example code while only paying half attention.
btw, no way my earlier example would actually fly due to multi-line blocks.
Probably something like this, but I don't think there is any consensus yet on how to terminate multi-line expression blocks which probably needs to happen before any kind of pitch like this can move forward.
var bar: String = do {
try fetchString() catch { throw InnerError.fetchOperationFailed }
} catch { // catch in the outer do
print("Today is not your day.")
insert_keyword_here "" // probably break, yield, etc.
}
agreed. this is something that pointfree tends to push hard, but it is not something the language itself pushes.
(based on what @esummers brought up earlier)
guard let result = try fetchString() catch {
throw InnerError.fetchOperationFailed
} else {
throw InnerError.noResult
}
guard let result = try fetchString() else {
throw InnerError.noResult
} catch {
throw InnerError.fetchOperationFailed
}
Only the first version should be permitted. Putting the catch after the else creates ambiguity about whether it covers the contents of the else block (and I don't think it should).
It should also be explicitly stated that when used in a guard statement the catch block is subject to the same requirements as the else block re. having to cause execution to exit the surrounding function.
Even given that, I do find it a little awkward to reason about the control flow when used with guard like this, because now you have three paths but only two of them are really obvious (those with explicit blocks, the catch and else). Ironically the happy path - no exception and a non-nil result - is the hardest to follow. It's possible that this is just unfamiliarity and that with exposure to this syntax it'll become intuitive.
Actually, I have no idea what happened to that example. The second one is clearly messed up, but I think you got the gist.
Sorry, I was so fixated on the order of catch vs else that I missed the other differences. I edited the examples in my post to more clearly frame it around the point of interest.
Honestly, in the specific case of multi-line do/catch, my biggest concern is that its really easy to miss that the catch is proceeded by a do() expression and not a do: block.
Something like the example you gave above is a perfect example of a miss-able assignment if you're just scanning the indents:
//more code here
//more code here
var bar: String = do {
guard let result = try fetchString() catch {
throw InnerError.fetchOperationFailed
} else {
throw InnerError.noResult
}
} catch { // catch in the outer do
print("Today is not your day.")
""
}
seems kind of like a logical mess to scan quickly if one has ever even used a block expression do-catch.
It's a similar concern to what I'd have with multi-line if/else()
With a one line do/catch or if/else, the exit or second expression will be reasonably close to the call, but in a multi-line do or catch it would not.
If you can be confident the code base only uses one or the other as maybe a linter enforced rule, I mean sure. Great no worries. But I don't know a lot of code bases that are that strict? And I don't generally read the linter rules before I read someone else's code, but perhaps I should.
I suspect throwing a bright pink return or other keyword at the bottom of those closures would make it harder to miss? Would that be acceptable in purer functional circles or a travesty?
Technically this is just a syntax thing and not an imperative vs functional thing. The same issue would occur in imperative style. I think you just need to get used to this in Swift since there are a lot of similar situations. Also this is a bogus example, not something you would see in real life.
What I'm hearing that a return or yield or other keyword on exit does not feel like an acceptable compromise to multi-line blocks-as-expressions to you?
FWIW, I like some of those syntax suggestions you deleted. I guess you didn't in the end!
I'm not sure what those were, but I personally feel that multi-line expressions should not be allowed to be an implicit return and require some other way to return from the block with a few exceptions. Namely guard statements should be allowed. I think this is the majority opinion and I'm in agreement with that.
The main reason multi-line implicit returns are allowed in function bodies are for Result Builders, but I think Swift should rethink that situation.
I actually like implicit return when it is clear something is an expression though...
Result builders are extra weird because if
evaluates like a block and the for
like a multi-line expression with an implicit return. Hence the saltyness about overloading of block commands as expression. I understand now that mult-line implicit returns are not what most people want for the main nonDSL/AST part of the language
That works for me. I will in fact just get used to the overloading.
Some of the proposed code examples (i.e. the #2 do {}, some of the do/catch statements) have multi-line implicit returns, so I gotta vote -1 in the mean time.