Why can't closures have labels and be more readable?

Okay, the example I used may be a poor one for the purposes of my point.

As I mentioned in the post, it's lifted and edited from a training video on Ray Wenderlichs site.

On that site they mention the (at least) 3 ways you can define a closure to run code (not just a one line multiply type closure)

And thus, my point.

I'm not asking for comments on the above code as per se production code etc.. It was merely a small amount of code to display a point...

Sure, but it would help to see an example of what you dislike that isn't obviously unnecessarily ugly. Mostly it seems you don't like in and would prefer the parameters to be listed outside of the braces. I actually don't disagree — it's not my favorite syntax in Swift — but I don't think it's actively a problem, whereas it seems you do.

2 Likes

It's not my only example. But I was being specific to closures and even more specifically regards to having the params / return type INSIDE the curly braces and still having to use the token 'in'

When both are unnecessary.

I'm sorry, that you, don't see that that is what I was getting at. I apologise for not being clearer.

Yes, I'm grumpy about this. I've got two degrees in CS / programming and years of C++ and Obj C under my belt, but the implementational syntax for closures, yes even from Obj-C, is horrible. That's my personal opinion and I'm allowed to say that.

I've seen you're a very, very smart, experienced and clever man, and you've worked on wonderful (and really complicated) stuff at Apple.

That you've taken such offence shows that you have taken this personally. My post was not a personal attack. But I'm not alone in believing how horrible the closure syntax is, and within a morning I offered a suggestion of how it could be cleaner and more readable.

And no one has yet to offer an informed reason why that suggestion couldn't be implemented or isn't better.

I believe you've had a huge hand in the tech we've all been using for years, and that's amazing, but perhaps, just perhaps, you're not seeing all this from an outsiders point of view. And if you wanted to, I'm offering that and saying just how damn confusing and hard to read Swift is, especially when different programmers shorten and tokenise the code 'because they think they're being clever or efficient' instead of letting the compiler do it, and that by making Swift allow this type of syntactical abuse be an integral part of the language it's utterly in opposition to all the claims made by Apple and the Apple documentation itself.

I fully appreciate how hard it is to create and develop and new language and compiler etc... beyond my abilities I'm sure, but it's the 21st century. Humanity can land robots on comets... but we can't make code readable and precise and fix closure syntax better than this!?

Hell we can't even have an Apple IDE that allows different bracing standards... (yes my personal hill I'll die on I'm sure).

1 Like

I really haven't taken offense. I asked you not to call things "stupid" and "awful" because we've put a lot of working into maintaining a culture of respect here and I don't want anything chipping away at that.

9 Likes

If I may, it seems there are two key issues for you here. The first is that the flexibility of Swift allows people you work with to write what you see as unreadable or overly dense code. That may be the case; however, I think every language enables writing incomprehensible code, and your background languages of Objective-C and (especially) C++ are no exception to that. The 'correct' fix to that in my mind is to have consistent guidelines among the people you're working with so that everyone is familiar with what to expect.

The second is that you view the current closure syntax as being worse than it could be. I don't think anyone – including the original authors of the syntax – thinks it's perfect, and maybe Swift could do better. Certainly, if you've got a concrete set of ideas for improvements then you're welcome to propose them. However, be aware that you'd be fighting against inertia here. There is a lot of Swift code already using the current closure syntax, and source compatibility matters. You'd need a comprehensive migration plan for all of that existing code which would need to be carried out over many years, and in that time you'd now have another alternative way of writing closures.

For my part, as a user of Swift, I learned the closure syntax and accepted it because it allowed me to express what I wanted to express. It's a little cumbersome at times, but I don't find it actively harmful.

1 Like

I've edited my post so you don't feel I'm chipping away at your culture.

That the caveat to making things right or better is such inertia and such a complex road to travel, means that bad syntax, bad design decisions will never get fixed because it's just easier to accept the bad ones.

Doesn't that just epitomise life and how society could be better if it wasn't just so hard to try and change it?

:(

1 Like

And as for " Joe_Groff

The closure syntax tries to serve a lot of different purposes"

No one has ever made a good solution trying to solve multiple problems in one go. Isn't that the core of Apple's business philosophy?

So if closures are a mess and complicated and you're trying to make one size fit all, then maybe the problem is with closures as a concept or the way they've been implemented?

Maybe several elegant but specific solutions were required instead of a mangled mess of one trying to do everything?

To be fair, the syntax you suggest can read as calling a curried function. Especially when the closure syntax predate SE-0002, that may not be ideal.

I do still do currying, though I can't say if it is prominent enough to obstruct your syntax, source-compatibility aside.

Thank you! I appreciate it a lot.

I don’t know what the post she deleted said, so maybe i’m wrong, but I don’t see where she just called the language stupid for no reason. surely there’s a difference between ranting and flaming. She is very much correct that swift’s closure syntax is suboptimal for anything that’s not a trailing function argument, us experienced swift devs just don’t run into this ugly part of the language all that often because we have local functions instead.

It certainly is unfortunate that we have two function definition syntaxes that do the same thing, though I think this is best solved by linting not language changes.
There are certainly ways the current syntax can be improved. i personally would like to see the following work

guard let i:Int = foo.firstIndex 
{
    ...
}
else 
{
    ....
}

which currently requires an awkward parenthesization around the predicate (who remembers the stdlib argument labels for predicate arguments anyway?) or the entire call site.

yay allman!!! <3 i swear i thought i was the only one here

1 Like

We don't need to re-open this part of the discussion.

3 Likes

Who knew Taylor Swift was so Leet at programming too! ;)

1 Like

karlie taught me !

1 Like

This was something we had tried, in fact. The original func() { } syntax for closures was the "full" syntax, which supported named arguments, multiple statements, and everything else, and the shorthand syntax { $0 + $1 } was constrained to the "shorthand" case where you had a single expression that didn't need named arguments. We had a couple usability issues with this design: because the short and full syntaxes were so different, users didn't realize they were the same thing. There also wasn't a smooth transition between the forms; as soon as you did anything that outgrew the abilities of the shorthand syntax, such as wanting to name your arguments or have multiple statements, you had to rewrite your code in the full form. Our current design evolved as a way to allow for both the fully explicit and fully implicit and compact case while allowing smoother transitions in between. (Whether it's successful or not, is of course totally up for debate.)

11 Likes

The issue is that what's clear to a human is not clear to a computer.

When a human reads code, their eyes bounce around. They spot delimiters and ignore their contents, they read later in the code and then go back to things they saw earlier, they recognize names they know and assume their meanings are obvious. In short, they apply contextual knowledge to their perceptions. This is what human brains are designed to do, and they do it so well that we don't even realize we're doing it.

That's not how a parser perceives code. It reads the code one "word" at a time, and it knows very little about the code it's reading. For example, it doesn't know that Int is a type—it just sees it as a generic "identifier". It will only discover later that there's a visible type called Swift.Int and decide Int refers to that type.

When Swift parses this code, here's how it will see it:

  1. let
  2. let <name>
  3. let <name> =
  4. let <name> = (
  5. let <name> = (<name>
  6. let <name> = (<name>:
  7. let <name> = (<name>: <name>
  8. let <name> = (<name>: <name>,
  9. let <name> = (<name>: <name>, <name>
  10. let <name> = (<name>: <name>, <name>:
  11. let <name> = (<name>: <name>, <name>: <name>
  12. let <name> = (<name>: <name>, <name>: <name>)
  13. let <name> = (<name>: <name>, <name>: <name>) ->
  14. let <name> = (<name>: <name>, <name>: <name>) -> <name>
  15. let <name> = (<name>: <name>, <name>: <name>) -> <name> {

Notice that, at step 13, the parser would think, "Oh, this is clearly a tuple". At step 15, it would think, "Oh, this is clearly a (malformed) function type." Only at step 16 would it have seen enough to realize that the previous eleven steps were a closure signature.

This kind of ambiguity is not insurmountable, but it's a problem. The parser needs to withhold judgment until it's found a clear indication of what it's been processing, then go back and apply that interpretation to what it saw earlier. When it's fed incorrect code, it won't know which construct you were trying to use, so it can't give a clear error message or even reliably point to where the problem occurred. Syntax highlighting and code completion won't know what to make of incomplete code. Sometimes achieving the design you want requires you to handle this ambiguity—for instance, when Swift sees a [ while it's trying to parse an expression, it doesn't know whether it's a dictionary or array literal until it reaches the first colon, comma, or closing square bracket—but it's costly. When possible, it's better to design the language to avoid that kind of ambiguity.

The closure syntax Swift actually uses is much less ambiguous in this way. The moment the parser sees { in an expression, it knows that it's parsing a closure. The complexity of the parameter list syntax for closures still makes things a little tricky, but it's a lot easier because there's an unambiguous marker that must appear before the part that's uncertain.

Balancing the very different ways that human frontal lobes and machine recursive-descent parsers "read" code is one of the challenges of language design. Sometimes you just have to go with something that's acceptable for both, even if it's not ideal for either.

12 Likes

assuming we all know what a parser is, i don’t see how this is much different from

// LET ID COLON LPAREN ID COMMA ID RPAREN
let a:(Int, Int) 

and

// LET ID COLON LPAREN ID COMMA ID RPAREN ARROW ID
let b:(Int, Int) -> Int

Function signatures have always looked just like tuples at the beginning, for reasons i thought we all buried back in the 3.0 days.

In this case, the leading colon tells the parser that it's definitely parsing a type name, not an expression; it's less the one-token lookahead after (Int, Int) that's the problem, it's retroactively reinterpreting what it originally thought were expressions as type names instead. But it's definitely true that type names are sometimes ambiguous, and there's some ugly, occasionally buggy code in the compiler to fix up those ambiguities.

1 Like

if we’re being real, the most obvious way to help the parser decide this early this is with a prefix func, but that already looks a lot like something we have in the language tbh

// what if we moved the `func` in front of `handler` and got rid of the 
// `let` and equals sign
let handler = func (a: Int, b: Int) -> Int
{
    // do Something else
    
    return someInt
}

still want to know your thoughts on

guard let foo = bar{ ... } 
else 
{
    ...
}
2 Likes

Maybe I'm just not "experienced" enough, but I tend to almost exclusively use closures instead of local functions (which look ugly and overly verbose to me). Perhaps this is just a matter of personal preference.

Actually, I find Swift's closure syntax quite nice, especially with its ability to provide as little or as much type information as possible (unless the compiler gives up). The one thing bothering me again and again is the confusion between a closure that takes n arguments and one that takes an n-tuple parameter, that is really rather unfortunate.

1 Like