Avoid curly brackets on single line bodies and computed properties

Auto-formatting would always put that on new line and consume 3 lines instead 1 (for shortest bodies).

Of course it'd be optional, like in Kotlin, so that if you like wrapping always between brackets, you can continue doing so

It's not the fault of the language if whatever auto-formatting tools you are using are mangling your code in ways you don't prefer, and I really don't think that new redundant syntax should be added just to stop them from doing that.

5 Likes

Kind of, since I'd like to have the possibility to skip the brackets completely

My observation was in reply of one the quoted comments suggesting the "workaround"

Could be:

public var elements: [Bool] <- [true, false]

And of course, the type could be inferred here also. Though maybe that is going too far.

public var elements <- [true, false]

I'm not very keen on this - purely from the point of reading code, it makes it somewhat unclear whether you are assigning a function/expression or the result of executing the function to the variable. Obviously you can tell from the type and, sometimes, the body, which it is - but I'm not sure the extra effort required and potential for confusing people new to the language is worth it.

I find...

public var debugDescription: String = String(describing: type(of: self)) + "(\(x), \(y))"

...to be the most potentially confusing. Surrounding the code in brackets makes it totally unambiguous that this will be evaluated every time you call debugDescription, rather than...when the object is instantiated? When it's first called? Every time?

4 Likes

What you describe here is a var that has an initial value, but which can also be assigned to; it will be evaluated once for the initial assignment.

On the other hand :

public var debugDescription: String
{
  return String(describing: type(of: self)) + "(\(x), \(y))"
}

… will be evaluated every time it is called.

public lazy var debugDescription: String = String(describing: type(of: self)) + "(\(x), \(y))"

… will be evaluated once only, on first call.

1 Like

Just to be clear, Kotlin doesn't replace brackets, it allows you to use either

Given Swift's opinionated standardization, I think I agree allowing = in a function definition is confusing and easily abused. However I also find it quite elegant in Kotlin to do things like

fun makeFullName() = "$firstName $lastName"
// or 
val fullName: String get() =  "$firstName $lastName"

I especially like it when using when, e.g. for an enum:

fun makeSizeLabel(size: SizeEnum) = when(size) {
    SizeEnum.SMALL -> "Small"
    SizeEnum.MEDIUM -> "Medium"
    SizeEnum.LARGE -> "Large"
}

Yes you can look at it as muddling the definition of a function/method, and I used to agree. What changed my mind for things like the above is taking it to mean "makeFullName() = ThisExpression".

This is why Kotlin's "everything is an expression" construct IMO means it makes sense in Kotlin, and may not make sense in Swift. I don't think there's an existing analogy you can intuit for what func thing() = would mean.

Still, personally I would still like a way for single expressions to be written more concisely in Swift. Maybe something like

func fullName() -> String in "\(firstName) \(lastName)"
2 Likes

I really don’t see how this form is more concise than

func fullName() -> String { "\(firstName) \(lastName)" }

also, conciseness isn’t everything, I like concise, expressive code, but not a the cost of potential ambiguity and less approachability.

Still, I like the idea that things like if and switch should be expressions (not everything, though, for example guard is great as it is, and it’s one of the things I miss the most in Kotlin): if they were, you could drop the ‘return’, which seem to me the real offender to me, that is, a lot of additional characters that are redundant and add noise.

1 Like

Ditto!

The more concise way would be

func fullName() in "\(firstName) \(lastName)"

but my guess is an inferred Type for a function result would see a lot of pushback.

A couple brackets isn't a huge deal, but that's also true of parens for if/switch statements among other things. It wouldn't be earth shattering, but I would prefer the ergonomics in some cases.

1 Like

I guess you have a valid point there..

It's indeed pretty plain and natural extracting the return at the end

return if(cond) a else b
return when(cond) {
    a -> x
    b -> y
}

Regarding guard I find the concept really fascinating, but the implementation quite poor..
having to explicitly type every single time else { ... return } is quite verbose and annoying.. I'd have expected a default nil for Optionals and plain return with Void (maybe passing a string in a autoclosure)

Once you understand what's happening, I agree it's verbose. However, if you're new to programming or new to swift, I don't think it would be possible to intuit what's happening. If I came to Swift and saw

func doAThing() -> Thing? {
    guard thing.isReady 

    return thing.doTheThing()
}

I would find it difficult to guess what's happening.
v.s.

func doAThing() -> Thing? {
    guard thing.isReady { return nil }

    return thing.doTheThing()
}

IMO it's much clearer and easy to reason about what might be happening, and makes it clear that author's intention is to return nil if thing isn't ready.

1 Like

I dont know, for me the keyword itself guard is quite significant and self-explanatory

But maybe it's because I tend to associate it to a sort of a runtime assert

Implicitly retuning nil for functions returning ‘Optional` would be really bad to me, and profoundly contrary to Swift core values and patterns.

I guess returning implicitly for functions returning void would be fine, but a little confusing for newcomers.

3 Likes

Having to explicitly write the else branch empty the usage of guard in the first place, for me

1 Like

that sounds like precondition to me.

Let's not get sidetracked about guard. It isn't return "every single time". Sometimes it's break or continue or fallthrough or throw.

This has been discussed other places, such as:

Inferred return for guard statement
An implicit return for guard
Proposal: Add implicit/default else-behaviour for the guard statement

No need to repeat all that here, right?

4 Likes

I'm against this because this will introduce difference in defining various code blocks. For example, Kotlin allows to drop brackets in single-statement if bodies, which wouldn't be possible in Swift because of absence of braces in if conditions. If we'll allow dropping brackets in function and property bodies then Swift will lose it's current uniformity which states that almost any new scope (except case body in switch statement) needs to be wrapped in curly brackets

4 Likes

The brackets { and } define a function, a closure or a block of code.
With return no longer required whenin single line function, the suggested things can pretty well be achieved as follows.. of course with using { and }:

public var elements: [Bool] = [x, y]

public func makeIterator() -> IndexingIterator<Array<Bool>>  { elements.makeIterator() }

public var debugDescription =  String(describing: type(of: self)) + "(\(x), \(y))" 

public func hash(into hasher: inout Hasher) { hasher.combine(SGLMath.hash(x.hashValue, y.hashValue)) }

public static func ==(v1: Vector2b, v2: Vector2b) -> Bool { v1.x == v2.x && v1.y == v2.y }

And this is all Swifty.
The typical guard statement in Swift is as follows (say this is initial code):

    guard let password = passwordTextField.text,
          password.count >= 6  else {
            return false
    }

This is how it will look transform with this suggested improvement. Please feel free to correct if I am wrong here.

    guard let password = passwordTextField.text,
          password.count >= 6  else 
          return false

This is a bit confusing. And later on when additional things needs to be done in guard else like below:

    guard let password = passwordTextField.text,
          password.count >= 6  else {
            showAlert()
            return false
    }

And it could incidentally, by programming error, become as follows and return false might go out of guard-else statement:

    guard let password = passwordTextField.text,
          password.count >= 6  else 
          showAlert()
          return false

One of the aspects when Swift was introduced was, Swift avoids common coding errors and { and } even for single line if statement. This was highlighted. I think we need to stick with this premise/aspect.

1 Like

Ok, putting my considerate hat on, I think it’s been documented a number of good reasons why this is probably not a good direction to go in due to inconsistency with the = operator’s existing use.

Taking my considerate hat off, what on earth is with all the proposals every 3 seconds to remove every conceivable keyword, piece of syntax, and practically everything from Swift? Everyone could come in and say “I don’t like this bit, let’s remove it” about literally any part of the language. Where does it end? :face_with_raised_eyebrow::man_shrugging:

8 Likes

This would create a stored property, not a computed one. In fact, it would do that in Kotlin, too. If we ported the Kotlin feature to Swift, you'd have to write:

Which hurts my eyes as a Swift developer, and also demonstrates that using = to define both stored and computed properties is perhaps a little too subtle.

18 Likes