Swift is so concise /s

Swift: "We don't like unnecessary parenthesis, or ; for that matter, too much writing".

Also Swift:

   func scan(...) throws -> Int {
        ...

        return try stringChars.withUnsafeBufferPointer({ charsPointer in
            return try withoutActuallyEscaping(delegate, do: { delegate in
                return try withUnsafePointer(to: delegate, { delegate in
                    ...

                    return doSomethingTmWith(delegate())
                })
            })
        })
    }

Joke aside, man, that's just ugly code, reminds me of NodeJS callback hell a lot.

1 Like

It can at least be reduced to this:

   func scan(...) throws -> Int {
        ...

        return try stringChars.withUnsafeBufferPointer { charsPointer in
            try withoutActuallyEscaping(delegate) {
                try withUnsafePointer(to: $0) {
                    ...

                    return doSomethingTmWith($0())
                }
            }
        }
    }

Perhaps more, depending on what's hiding in those ...

But in general you're right: dealing with pointers is not concise in Swift. That's mainly because it needs to know the scope of each by-pointer access.

6 Likes

Yes, but then you're just adding cognitive load to whoever reads and maintains the code: Why does the first method has a return and the others no? Same thing with $0.

One of the main problems with Swift is the assort of extraneous stuff you just don't need, for the sake of brevity. Like the learning curve is steep with no reason other than to save keystrokes, which in this day and age with autocompletion and auto-fixing from IDEs shouldn't be a concern to anyone.

3 Likes

There's only load until there's not. You have the load now, but I don't anymore. You can offload it to automatic thinking.

The pattern is: use return when there's nothing else above the line, in the same scope.

If there's a return, otherwise, now I see it as noise.

Kind of agree here, but

  1. Sometimes names are useless and we should have a way to avoid them, in favor of indexes only. (This applies to functions, but the feature has never made its way there. Pretty terrible for operators—people end up using "lhs" and "rhs". Offensive to people who don't have two hands.)
  func min<Comparable: Swift.Comparable>(
    by getComparable: (Element) throws -> Comparable
  ) rethrows -> Element? {
    try self.min {
      try getComparable($0) < getComparable($1)
    }
  }
  1. The right thing probably would have been a decoration for the parameter. (Typing the name twice is not a good solution for single-liners, so at least that's taken care of somehow.)

Got any ideas for what that should have been?

2 Likes

"On the other hand..." :crazy_face:

Pretty terrible for operators—people end up using "lhs" and "rhs". Offensive to people who don't have two hands.

1 Like

Makes me wonder ... why doesn't swift have 'last-statement-implicit-return' everywhere? It seems like it really should have that -- along with a facility and requirement to explicitly mark contexts where 'return void' is the intention ...

Most func's/closures should return something in general ... Marking those closures which don't return anything with a keyword to make them more obvious would make a lot more sense to me than it does to mark all the places that are returning something ... The function's/closure's which don't return anything are probably mutating something or causing a side effect -- and therefore probably the ones that require the most careful inspection when looking at a pile of nested closures anyway ...

And it would be nice not to have to go through the transition annoyance of adding return keyword everytime you convert a single line closure expression to a multi-line closure expression ...

2 Likes

This is what Rust does (actually 'last-expression-implicit-return'), and it seems to be a reasonable design, but different from what Swift has currently.
How Rust marks "return void" is to end with a semicolon.

Once you have more than one possible end to a statement, this feature becomes a source of logic bugs, where something is returned unintentionally from some statement a few levels deep. We see it in Ruby fairly often. Rust has an easier time of it due to the static typing checking whether it even makes sense to return at a certain point. But personally I find the value of the feature much less in complex statements, and it's rather difficult to reason about without knowing all of the language's semantics (e.g. Is this something that can implicitly return?). Swift's current approach cuts the line between the two is a way that gets most of the benefits without most of the downsides.

7 Likes

It does look... nested. Though I'm not sure where the problem lies. The code itself is hopping through many unsafe hoops, raw-pointer manipulation, unprovable non-escaping closure, etc.

Ideally, one probably doesn't code like that, but it's sometimes inescapable to shave off precious time, or that the interop demands it.

1 Like

The "real solution" to something like this is probably something akin to Rust's lifetimes system, so that withUnsafeBufferPointer and withUnsafePointer(to:) could be replaced with versions that return a pointer with a lifetime linked to the original String, instead of taking closures.

3 Likes

Pretty much all the syntactic overhead here seems to be getting a temporary pointer to the delegate, which means what you're actually calling out is the verbosity of using some C API that takes a function pointer and a void* context. That's maybe not an unfair point, but it's worth pointing out that you presumably only need to suffer this once, in a Swift wrapper around the C API.

10 Likes

I like the closure syntax. Its a nice way of letting people know, that the pointer is only valid in a certain scope and escaping it could lead to disasterous results.

I think, that if you want to take manual control over the lifetime of a pointee, you probably should allocate your own UnsafeMutableBufferPointer and initialize it accordingly. This could be packed to a nice extension (ie on String).

I don’t think manual control would be possible anyway, since a lot of times referenced memory is not on the heap.

Not a big fan of this. When you scan a big function with the question "what does it return?" it's much easier to spot the return statement(s).

Single-line functions though look natural without the return because they are closer to expressions. You don't have return in expressions, do you? In the case of single-line functions the meaning of { and } is more evaluate the expression every time than a function body.

In other words, I actually like the way it is in Swift currently :stuck_out_tongue:

3 Likes

I don't know where you read that a goal of Swift is to be concise or avoid writing, that seems to clash with the API Design Guideline, e.g. “Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters”.

This seems to be the opposite of the original complaint, so I suppose it's both too verbose and too concise.

2 Likes

I want to answer these two together. No, the cognitive load doesn't ever goes away. Why? Simple, cause now you're parsing the code yourself to find out what the last statement is. In languages with mandatory ; that's easy, here in Swift, it's not, like none at all. A return is a pretty clear marker of the end of the scope and the expression returned, it doesn't matter what you don't have to think much more than that, just find the return. As for until there's not I've been a software engineer for over 12 years now and I still Google syntax and stuff. The less stuff I have to remember, the better, the more I can understand at a glance, also, the better.

Yes. The issue is Swift is trying to protect programmers from themselves, in my opinion way too much. This is a compromise between what the language allows and the perceived safety it provides.

Oh, but one does, all the time, and without doing a Github search I'm pretty sure I can find even worse things around.

Yes, but, I'll let you know the next time I don't have to deal with C and have to be extra verbose because of Swift wanting me to write what Swift thinks is safe code.

sure.

It'd be the opposite. You're forced to be extra verbose because Swift thinks what you're writing is unsafe.

6 Likes

First, it wasn't a complaint, the idea was to point out how the search for brevity clashes with Swift being too smart for its own sake.

The code style preferred for Swift is one that favors brevity. You can see it in: $0, trailing closures, implicit returns, etc.

To be clear, that's not at all what I was saying in the text you quoted. I was describing a system in another language that is more complex, but allows this pattern to be expressed in a cleaner way with the same safety.

1 Like

It looks to me like you're trying to prove a point on a bit of code you have written in order prove a point.

Swift has many features - like C interop, dynamicCallable, manual memory management, inout etc. Those features are imo here to provide a power needed to create great libraries. Having all of those tools does not mean, that it is correct to use them at any instance.
The strong point of Swift is, that you can let your juniors cooperate on a project without fear, that they miss something in calling conventions and merge something, that corrupts stack in aweful way - ruinintg day for everyone at some point.
I think that the biggiest lack of safety right now is concurrency - which is being addressed.

I know, I understand, what I'm pointing out is that Swift doesn't allow that, or well, it does but it shows a warning because it's trying to prevent people from shooting themselves in the foot.

The warning is: Initialization of ... results in a dangling pointer.