Stream: ideas

Topic: Explicit block end delimiters?


view this post on Zulip Anthony Bullard (Dec 19 2024 at 02:58):

This is a placeholder that I wanted to post so I don’t forget to tomorrow.

TL;DR: with static dispatch and new lambda syntax, I felt like a move towards explicit block ending (through say an end keyword) may be appropriate.

Feel free to attack this idea and me personally(that’s a joke, be nice) in the meantime, I’ll flesh it out and provide rationale in the morning.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:32):

I've converted alot of my AOC 2024 Day 9 solution over to my understanding of what Roc syntax may very well look like in 3-6 months. This PR shows what that conversion looks like:

https://github.com/gamebox/aoc-2024/pull/1/files?diff=split&w=0

I adopted:

  1. Camel case
  2. Var
  3. For
  4. Static Dispatch
  5. New lambda syntax
  6. Main being a function taking a param

Look at the split view, and then look at the new style in isolation.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:45):

And this PR adds end block delimiter keyword to if/else, when, and multiline lambdas.

https://github.com/gamebox/aoc-2024/pull/2/files?diff=split&w=0

Again look at the split view, and then the new file in isolation. I don't know exactly way, but it helps my eyes track the control flow much better than without. I don't know if parens and commas magically changes my perspective on this - I didn't really feel the need for this before.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:48):

Here's a bit of syntax before:

 blocks.walk_until(([], Err(NoFreeSpace)), |(acc, free_result), b|
                if !(block_is_free(b)) then
                    Continue((acc.append(b), free_result))
                else
                    Break((acc, Ok(b)))
            )

and here's with end delimiters:

            blocks.walk_until(([], Err(NoFreeSpace)), |(acc, free_result), b|
                if !(block_is_free(b)) then
                    Continue((acc.append(b), free_result))
                else
                    Break((acc, Ok(b)))
                end
            end)

But I think it shows itself way better in a longer, more complicated function

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:51):

This specific function is what drove me to even consider this:

https://github.com/gamebox/aoc-2024/pull/2/files#diff-60219f9c7c9c52d946181404188983fefc7cdc87303f6104c9723ed0719fd366R55

Maybe one could argue: "That's your fault for making this function so long, break it up!". Do we want syntax to make it inconvenient for people to write complicated logic into a single long function, or do we want the code to be easy to read however the programmer decides to code?

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:52):

A question I have is (without a value judgement on my part), would having this lessen or remove the need for strict whitespace significance? I know there is a contingent of programmers who absolutely loath WSS (most people that would be drawn in by static dispatch that aren't fans of Python).

view this post on Zulip Anthony Bullard (Dec 19 2024 at 11:54):

Anyway, Richard said this is a safe space to explore, and all ideas are valid. So please don't shun me - I know this is a large change to a fundamental bit of character of Roc syntax. But so is Static Dispatch :smile:

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:50):

Man, just as you said, this helps for long blocks, and I find it overly verbose for short blocks. Which to choose...

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:52):

I think a lot of short blocks today, with static dispatch, will become single expressions (static dispatch chains)

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:53):

That's brings up a question: what's the logic here? Presumably it's not just code blocks, but specifc constructs that contain code blocks, e.g. functions, for, if, when?

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:53):

Yeah

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:53):

I need to formalize it a bit

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:53):

Because your example code doesn't have end after multiline assignments

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:53):

A block contains more than a single expression

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:53):

But ends in one

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:54):

From a parsing perspective, a block is anything that does newline + indent for a bit

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:54):

Yeah today

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:54):

This would change that I believe

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:55):

But you would prefer to have end only if there's more than just an expression, even with indentation?

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:55):

I think I want to gauge the initial sentiment to the concept right now

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:55):

Sam Mohr said:

But you would prefer to have end only if there's more than just an expression, even with indentation?

Yes

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:55):

Like I said, this could be a way to get away from WSS

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:56):

Not saying that’s desirable

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:56):

For some it’s not

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:56):

For many others it is

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:56):

I know a lot of people hate Python because of WSS, and many more just live with it when using python

view this post on Zulip Sam Mohr (Dec 19 2024 at 12:57):

I don't know a formal definition of White Space Syntax, but this still feels like WSS. In fact, you can tell it comes from WSS languages because this is the first time I've seen end immediately followed by a closing parentheses

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:57):

I used to be that way, but Elm, OCaml, Haskell, and F# changed me

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:57):

No, this is an alternative

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:58):

Similar to Ruby and Elixir

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:58):

Both languages known for having “clean” syntax

view this post on Zulip Anthony Bullard (Dec 19 2024 at 12:58):

Which I take to mean, easy to read but not symbol heavy

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:00):

Do we want syntax to make it inconvenient for people to write complicated logic into a single long function, or do we want the code to be easy to read however the programmer decides to code?

This somewhat assumes that programmers make decisions for good reasons IMO. I don't like every decision that Roc has made to discourage bad behavior (I'm looking at you, Str.len()), but I think they're good things to do. If not having end requires people to break their code up more readably, I would consider that a plus.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:00):

I should make it clear that this is not something I would kill for, but I think for adoptions sake it’s a space worth exploring

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:01):

Yep! I know you're generally voicing that, but I've been assuming a hypotheticals helmet for the past few discussions when possible

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:02):

Sam Mohr said:

Do we want syntax to make it inconvenient for people to write complicated logic into a single long function, or do we want the code to be easy to read however the programmer decides to code?

This somewhat assumes that programmers make decisions for good reasons IMO. I don't like every decision that Roc has made to discourage bad behavior (I'm looking at you, Str.len()), but I think they're good things to do. If not having end requires people to break their code up more readably, I would consider that a plus.

The thing I don’t like about this is we are saying “write code the way I THINK is readable, and trust that Roc will make it just as performant as it would be if you wrote it the way that feels natural to you”

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:02):

I think most languages do that, and the ones that don't get reputations for being insane kitchen-sink languages (C++)

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:03):

Some people just really like reading a function top to bottom and not navigating to 20 different custom functions to understand what it’s doing

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:03):

Yeah, I would count myself among those people

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:03):

Hey don’t you bring C++ into this, I thought we were friends

view this post on Zulip lue (Dec 19 2024 at 13:03):

I feel like taking this to the logical end, we'd have similar syntax to rust or gleam with { } for grouping (which seems slightly more intuitive than end for people I imagine bc it autocloses etc but also has the horrible effect of overloading record (punning) syntax so maybe not).
I don't really hate it but to me it doesn't solve actual sources of confusion with significant indentation (case patterns and let declarations)

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:03):

I specifically don’t want braces

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:04):

The aren’t necessary

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:04):

End can auto close as well

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:04):

But I think there are simpler ways around that. I'm pushing for "flatter" code by disincentivizing nesting, but length isn't inherently bad IMO. You can always require double newlines between top level defs a la Python to make it clear where defs end

view this post on Zulip Anthony Bullard (Dec 19 2024 at 13:04):

And editors can be made to understand it’s a block

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:09):

I would be surprised if we got support for end being inconsistently added to language constructs depending on the length of their content (and I wouldn't like that from a vibes perspective), so we should probably do it for all control flow or for the ones we expect to be long.

if and when can always get pretty long, but being expressions and statements, I'm less sure that will happen in a hard-to-read way. The main candidate I'd be willing to try is for because it can only be used as a statement, meaning it can't end a function body, and it already has do at the end of it, making for X in Y do ... end a logical step

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:10):

I think that top-level functions never need end because we can just have double newlines if we really need it

view this post on Zulip Sam Mohr (Dec 19 2024 at 13:11):

I'm not sure how I feel about inline nested function bodies. They can be a good candidate if they're not the last argument in an arg list

view this post on Zulip Brendan Hansknecht (Dec 19 2024 at 15:36):

Anthony Bullard said:

The thing I don’t like about this is we are saying “write code the way I THINK is readable, and trust that Roc will make it just as performant as it would be if you wrote it the way that feels natural to you”

I don't follow this comment. Isn't that just what having an opinionated language is? We surely don't want to support all styles of how to write code. We also want to push people towards the pit of success where possible. There also is clearly an aesthetic that we would hope most roc code follows such that it is readable to the largest number of people.

Also, code readability and performance generally have little to do with each other. Most performance comes from the fundamental algorithm and data design. That can be written into any code format.

view this post on Zulip Brendan Hansknecht (Dec 19 2024 at 15:40):

Then on a more personal side, I really dislike end and would pick significant whitespace or braces keyword for this.

Significant white space is amazing with a good formatter. Braces are clear while avoiding verbosity. I find the end keyword to be noisy and less flexible.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 16:34):

Cool I think that’s often true. But I think it’s easy to get lost visually while scrolling through long blocks - and I think it affects some people more than others.

view this post on Zulip Karl (Dec 19 2024 at 16:42):

Most editors have a number of options for visible whitespace or vertical guides. I use them at low contrast for this exact situation.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 16:50):

My editor doesn’t show guides unless you have a block (Neovim)

view this post on Zulip Anthony Bullard (Dec 19 2024 at 16:51):

And maybe a shortcoming of current tooling , but WSS blocks in Roc today are not recognized reliably as a block

view this post on Zulip Anthony Bullard (Dec 19 2024 at 16:56):

Brendan Hansknecht said:

Then on a more personal side, I really dislike end and would pick significant whitespace or braces keyword for this.

Significant white space is amazing with a good formatter. Braces are clear while avoiding verbosity. I find the end keyword to be noisy and less flexible.

Two questions so i understand your perspective:

1: why prefer braces (more symbols) over end?
2: why do you view end as “less flexible”?

view this post on Zulip Karl (Dec 19 2024 at 17:04):

A quick search turned up this plugin for nvim. I haven't moved off vim so I use a different one (indent-guides).

view this post on Zulip Karl (Dec 19 2024 at 17:06):

though admittedly I've mostly been in vscode for the past few years with helix for terminal use

view this post on Zulip Brendan Hansknecht (Dec 19 2024 at 17:12):

1: why prefer braces (more symbols) over end?

You essentially always have a start symbol of some sort. That is just standard: then, is, ->, =. For other languages like python you have :. So all of those get replaced with {. Then instead of having an end keyword you just have the matching }.

I also find {} easier to both ignore and parse than something .... end. Easier to parse cause it is a symbol instead of a keyword. So it stands out instead of blending with variables and such. Easier to ignore cause it can be colored in a way to make it minor. Also, just to clarify, I much prefer our significant whitespace (with a good formater and parser) than braces.

2: why do you view end as “less flexible”?

Doesn't work on a single line. Can be less aesthetically pleasing and noisy in certain setups. As such, you may want to write your code in an even more strict format to make it look good with end.

view this post on Zulip Anthony Bullard (Dec 19 2024 at 17:59):

Brendan Hansknecht said:

1: why prefer braces (more symbols) over end?

You essentially always have a start symbol of some sort. That is just standard: then, is, ->, =. For other languages like python you have :. So all of those get replaced with {. Then instead of having an end keyword you just have the matching }.

I also find {} easier to both ignore and parse than something .... end. Easier to parse cause it is a symbol instead of a keyword. So it stands out instead of blending with variables and such. Easier to ignore cause it can be colored in a way to make it minor. Also, just to clarify, I much prefer our significant whitespace (with a good formater and parser) than braces.

2: why do you view end as “less flexible”?

Doesn't work on a single line. Can be less aesthetically pleasing and noisy in certain setups. As such, you may want to write your code in an even more strict format to make it look good with end.

Thanks for the thorough and thoughtful response!

view this post on Zulip Anthony Bullard (Dec 19 2024 at 18:01):

I’ll give this topic a couple of days to allow others to comment. And if no one comes out with a passionate defense, I’ll mark as resolved with a big fat “NO THANKS”.

view this post on Zulip Brendan Hansknecht (Dec 19 2024 at 18:13):

I should caveat. I have not substantially used a language with end or similar in probably a decade. So I may just be too biased by what I am used to. (also, I have seen plenty of horrid code with whitespace, but generally with a formatter I find it nice)

view this post on Zulip Richard Feldman (Dec 19 2024 at 18:35):

I don't have strong feelings on this at the moment. Both look fine to me, which makes me default to preferring the more concise one. :big_smile:

view this post on Zulip Richard Feldman (Dec 19 2024 at 18:36):

I also wouldn't like it inline (list.map(|x| x + 1 end)) but also wouldn't like it being necessary in multiline but not in single-line

view this post on Zulip Richard Feldman (Dec 19 2024 at 18:37):

might be a different story if the question was "end versus significant whitespace" but we use significant whitespace in more areas than that (e.g. when branches and nested defs) so the selling points seem to be entirely aesthetic unless I'm missing something!

view this post on Zulip Anthony Bullard (Dec 19 2024 at 20:22):

Just to add some color around single-line and multi-line blocks (i.e., just an expr). This contains a lot of code examples from Elixir, which is just one language. It also contains a lot of Steel-manning of the pro-end-delimiters' arguments. It should not necessarily be considered my own position on all subject (though I am empathetic to them).

Elixir allows this:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

as well as

defmodule Math do
  def sum(a, b), do: a + b
end

So to me I think that parsing:

|a, b| a + b

Would be unambiguous with or without WSS. And I'm not sure if when branches need WSS either

But

|a, b|
  c = 2
  a + b + c

is harder to disambiguate without WSS (and that means everything being perfectly aligned along the leading edge).

|a, b|
  c = 2
   a + b + c
end

Is perfectly unambiguous even though there is a whitespace misalignment. In my opinion, if Roc lets you have type errors in dev and still build and run regardless - why would we want to fail _parsing_ for a one-space misalignment?

There is actually a real reason for I think for Elixir to have end explicitly. It's an impure language, and a block does not have to end in an expr. For instance, this is valid (but dumb) Elixir:

 defmodule Foo do
   def something() do
     i = 1
   end
 end

I _personally_ don't see the ends there as noisy or superflous, as for me they become useful syntax. I think you can even have the do/end pairs as minimal as you could braces. They are also recognized as braces by all editors. And I think that's very powerful for some people, but easily bop to the top and bottom of a block with a keypress (I use Neovim, btw).

So code navigation and an unambiguous symbol that a block has ended _can_ help _some_ people with readability. Elixir is praised for having a nice syntax that many enjoy (especially former Rubyists, but that may be familiarity bias for that group).

view this post on Zulip Anthony Bullard (Dec 19 2024 at 20:24):

And yes, I understand that a lot of the above arguments could be made for squirly brace-wrapped blocks

view this post on Zulip Eli Dowling (Dec 21 2024 at 07:05):

Anthony Bullard said:

And maybe a shortcoming of current tooling , but WSS blocks in Roc today are not recognized reliably as a block

Yup with each new addition to roc the tree sitter parser gets more cooked. :sweat_smile:
But we can absolutely fix that without much trouble. Assume for the sake of this discussion that neovim and all other editors correctly recognise roc blocks

view this post on Zulip Eli Dowling (Dec 21 2024 at 07:09):

I am with @Brendan Hansknecht and strongly prefer {} over end for non wss syntax.
It's shorter to type and shorter to read.

view this post on Zulip Anthony Bullard (Dec 21 2024 at 12:38):

Eli Dowling said:

Anthony Bullard said:

And maybe a shortcoming of current tooling , but WSS blocks in Roc today are not recognized reliably as a block

Yup with each new addition to roc the tree sitter parser gets more cooked. :sweat_smile:
But we can absolutely fix that without much trouble. Assume for the sake of this discussion that neovim and all other editors correctly recognise roc blocks

This is not the case unfortunately, at least not reliably for things like indent guides

view this post on Zulip Eli Dowling (Dec 21 2024 at 16:02):

Sorry I don't understand what you're saying @Anthony Bullard. Are you suggesting we can't fix the tree sitter parser to correctly provide code block understanding?

view this post on Zulip Anthony Bullard (Dec 21 2024 at 16:27):

Eli Dowling said:

Sorry I don't understand what you're saying Anthony Bullard. Are you suggesting we can't fix the tree sitter parser to correctly provide code block understanding?

No, that it doesn’t do it well in Neovim todag

view this post on Zulip Eli Dowling (Dec 21 2024 at 17:44):

That really surprises me. Like indent guides just come from setting the editor to 4/2 spaces normally?
Other than that it's just code folding right?
Or are you expecting some block functionality that you don't currently have?

(Helix and I'm pretty sure zed, can ofcourse select by "block" as a text object, but neovim definitely has plugin for using text objects in commands)


Last updated: Jun 16 2026 at 16:19 UTC