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.
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:
Look at the split view, and then look at the new style in isolation.
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.
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
This specific function is what drove me to even consider this:
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?
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).
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:
Man, just as you said, this helps for long blocks, and I find it overly verbose for short blocks. Which to choose...
I think a lot of short blocks today, with static dispatch, will become single expressions (static dispatch chains)
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?
Yeah
I need to formalize it a bit
Because your example code doesn't have end after multiline assignments
A block contains more than a single expression
But ends in one
From a parsing perspective, a block is anything that does newline + indent for a bit
Yeah today
This would change that I believe
But you would prefer to have end only if there's more than just an expression, even with indentation?
I think I want to gauge the initial sentiment to the concept right now
Sam Mohr said:
But you would prefer to have
endonly if there's more than just an expression, even with indentation?
Yes
Like I said, this could be a way to get away from WSS
Not saying that’s desirable
For some it’s not
For many others it is
I know a lot of people hate Python because of WSS, and many more just live with it when using python
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
I used to be that way, but Elm, OCaml, Haskell, and F# changed me
No, this is an alternative
Similar to Ruby and Elixir
Both languages known for having “clean” syntax
Which I take to mean, easy to read but not symbol heavy
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.
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
Yep! I know you're generally voicing that, but I've been assuming a hypotheticals helmet for the past few discussions when possible
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”
I think most languages do that, and the ones that don't get reputations for being insane kitchen-sink languages (C++)
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
Yeah, I would count myself among those people
Hey don’t you bring C++ into this, I thought we were friends
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)
I specifically don’t want braces
The aren’t necessary
End can auto close as well
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
And editors can be made to understand it’s a block
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
I think that top-level functions never need end because we can just have double newlines if we really need it
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
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.
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.
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.
Most editors have a number of options for visible whitespace or vertical guides. I use them at low contrast for this exact situation.
My editor doesn’t show guides unless you have a block (Neovim)
And maybe a shortcoming of current tooling , but WSS blocks in Roc today are not recognized reliably as a block
Brendan Hansknecht said:
Then on a more personal side, I really dislike
endand 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”?
A quick search turned up this plugin for nvim. I haven't moved off vim so I use a different one (indent-guides).
though admittedly I've mostly been in vscode for the past few years with helix for terminal use
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.
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 anendkeyword you just have the matching}.I also find
{}easier to both ignore and parse thansomething .... 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!
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”.
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)
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:
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
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!
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).
And yes, I understand that a lot of the above arguments could be made for squirly brace-wrapped blocks
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
I am with @Brendan Hansknecht and strongly prefer {} over end for non wss syntax.
It's shorter to type and shorter to read.
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
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?
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
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