Break <boolean-expr>

I show an interesting syntax sugar in Crystal Lang (https://crystal-lang.org) - which is, btw, an interesting one to look upon, as it is quite concise and clear language.

Anyway, what about adding the following:

break if (a > 0)

It is just for the clarity, instead of

if (a>0) break

In order words: break <boolean-expression>

Why do I find it better? Because the break is more visible. In the body loop, it becomes a bit more clear where the break is. I was working with some crystal code, and it really made a difference.

Idea is interesting, especially because I personally highly prefer writing code that puts things like return or variable assign to the beginning of a code flow, e.g.:

return if (cond) {
    ...
} else {
    ...
}

Instead of:

if (cond) {
    ...
    return ...
} else {
    ...
    return ...
}

The first code sample is much clearer to me.

Still, I think this suggested syntax is a very “heavy” addition. It is not some small improvement, it changes the whole concept of how to read the code. It is more like a revolution than evolution.

Right now, if a line of code starts with a statement / operation, then this operation is always (?) unconditional. If a line starts with a = then there is no way it won’t assign a (I ignore exceptions). Also, all special keywords and statements generally affect what is on their right side, not left side. The code is generally read from the left to the right.

Your suggested change is inconsistent with pretty much everything that already exist in Kotlin. Or maybe I’m missing some similar constructs?

Something similar Perl does. It hard to read but I find. Demonstrates the problem I hope this!

Specifically, I find it far too easy to miss conditionals when they’re tucked away at the ends of lines. Kotlin code can be scanned very quickly to get the rough idea of what’s going on, because the structural stuff is generally at the starts of lines — so even without reading the rest, you get a feel for how the code is structured and which bits you need to look more closely at. (@broot, in your example, I notice you prefer the version which adheres to that.) Conditional suffixes break* that.

In OP’s example, both parts are structural — but I’d never put them both on the same line, for precisely that reason. I always put the body of an if statement on a new line:

if (a > 0)
    break

I find that easier to read anyway, but it’s particularly important in cases like this. (I’m happy to put if expressions all one line — but only if they’re not too long, and don’t include structural keywords such as return.)

(This change would also give two different ways to write exactly the same thing — a step down the road that leads to everyone using their own, separate dialect and it being much harder to share code…)

As broot says, this would be a pretty drastic change to how code can be structured and read, and I’d want to see a much stronger justification for it.


(* Pun intended.)

I don’t see the change as that drastic. And it is optional, regular break should still exist, no one is questioning that.

I am just sharing my experience with Crystal. To be honest, I didn’t expect to like it; however, at the end I just found that I was appreciating this code more:

break ifFooBar()

then this:

if (fooBar()) break

break is more like applying the map() on the break container having the true as a default:

Break(true).map{ Unit -> boolean }

Right? :slight_smile:

That is always the case with general-purpose languages. One may say the same for e.g. collections and sequences (I know, they are not different, but often I just want to damn iterate and not think about the differences). Or for the elvis operator - it does pretty much the same as if-else. For loop is a specification of the while loop, which is all the same, right? And yet, we should lean towards generateSequence (such a long name!) instead of using any loop at all :slight_smile: Look also for the operator invoke() - even I sometimes use invoke() explicitly: Foo().invoke() instead of Foo()().

TLDR: syntax sugar is exactly that - a different way of doing the same; but in a more understandable manner. In this case, everyone will understand the difference; and the readability of the code (one of the most important traits) will remain at least the same.

Again, I am sharing my experience with Crystal. I am not favoring it over Kotlin, nope; and I didn’t even know it exist until last week, but I would it was very readable. The break syntax actually helped. When I do scan the unknown code, I want to first grasp the major flow, hence all IFs Breaks etc are getting in the way. That being said, I appreciated reading break <something> and simply skipping that part while scanning code. In the opposite case, reading if (a) would require mental processing, just to go to the next line that says break.

Anyhow, just a simple idea.

1 Like

Sorry, I’m confused with this part. break is for control flow, it is similar to return, but I don’t see how it is similar to invoking a function.

1 Like

That example is written in the following manner: break is essentially a container of a boolean value that indicates that some flow has to break. As being a container, it may be a Functor with a map(). All control-related keywords could be containers, probably.

Another angle: look at the yield(value). It emits value in its scope. The same is: break(value). It emits a boolean for the scope of the loop. yield should be a keyword, too (or break could be a method). Actually, if you think, break is yield(true):slight_smile:

Another angle : if could always be added after expression. Instead of saying if (foo) a = 7 you could say a = 7 if (foo). This maps logically to following: container(lambda).invokeIf(expression). It’s the other way around. I know, I know, this is not how we used to think; but look, we are all learned to think that algorithms blocks are the (only) right way of constructing code from the very first days of our dev lives (a topic out of scope of this article). But would it be readable? Dunno. At least it would force having smaller if expressions, and that is a good thing (huge if blocks are really a pain).

Please don’t get me wrong, just trying to give perspective here :slight_smile: That is why I am trying different languages all the time, and trying to figure out what might work. Have to go back to work, sorry; unfortunately, my daily tasks are not interesting as this discussion.

1 Like

Yes, this is actually my point. I don’t see a reason why we should have break if (cond) and not return if (cond), foo = 5 if (cond), doSomething() if (cond) or maybe even for (item in items) if (cond) {}. So we duplicate the whole syntax of the language, make the language more complicated and ambiguous only to allow developers to choose according to their personal taste. Then while reading the code we always have to look for both syntax options.

For me this is like writing a book in both left-to-right and right-to-left, switching between them with each line. I appreciate people experiment with different ideas when designing a programming language, especially if this language is new and have a specific purpose. But that doesn’t mean we should pick existing language and add random features that are inconsistent with its whole design. In programming languages less is often more.

2 Likes

And about yield - this is a digression to the whole “conditional code” topic, but no, I don’t think yield has anything to do with break.

Technically speaking, yes, all suspend functions in Kotlin could jump out of the current execution context. But this is a workaround that Kotlin team had to implement to allow running coroutines in the environment that doesn’t support them. Conceptually, yield just provides an item to the consumer and optionally waits for it. It doesn’t go anywhere.

In Kotlin there are very few ways to “jump out” or break from the code context: return, break, continue, throw. And only throw is propagating, for the first three you have to use the statement directly in your code. This is to make the language more expressive, explicit.

Same for functors. Kotlin is not a functional language. It has many elements of functional languages, but it isn’t one of them. Therefore, the control flow is very explicit.

1 Like

Sure, agree with that. Again, I am just expressing my findings. I was surprised as well, but there was a bit more clarity (for me), which I understand where it comes from. I do understand the point to keep things as it is

just a note - both communicate with the scope they are executed in. yield provides values, break breaks the scope flow.

1 Like