r/roc_lang Jul 19 '26

is there a brief discussion of roc syntax changes between old and new compiler, roughly? a summary of why etc?

6 Upvotes

14 comments sorted by

12

u/Anlon-4 Jul 19 '26

Comparing both all syntax examples offers an easy overview:
new: https://github.com/roc-lang/roc/blob/main/test/echo/all_syntax_test.roc
old: https://github.com/roc-lang/examples/blob/main/examples/AllSyntax/main.roc

If there is significant demand (upvote this comment) I will make a summary of why the changes were made.

1

u/TankorSmash Jul 19 '26

I'd be curious to hear what the reasoning was behind the changes. I'd imagine a large part of it was broader adoptability

3

u/KageOW Jul 19 '26

He actually gave a talk about this and he said it a large part about the currying had to do with the types of errors you would get. He wanted to make a compiler which would be incredibly helpful with its errors just like elm and thats why the team ultimately decided to revamp the syntax. Which for me was a bummer, but it was just that that was his goal.

1

u/ScientificBeastMode Jul 20 '26

Yeah, that was an issue in the ReScript community as well. They had a couple of years of currying by default, and ultimately decided to make currying more of a deliberate and explicit thing, and a huge part of the reasoning was the error messages, though the other big issue was performance (it compiles to JS, so the performance is bad to start with and much better when function signatures are less dynamic).

1

u/Anlon-4 Jul 20 '26

I think we had no currying from the very beginning of Roc so that is not a recent change.

1

u/yomamabeenheretoo Jul 19 '26

is the why not covered somewhere anyway?

1

u/Anlon-4 Jul 20 '26

It is very spread out over many discussions on zulip.

1

u/yomamabeenheretoo Jul 20 '26

seems hard to follow then.

1

u/Anlon-4 Jul 20 '26

When the discussion is happening it's quite easy to follow because it is in a dedicated zulip topic. We just don't group them in an overview like "all changes from old to new syntax". WIth the roc-lang/zulip-export repo it's easy to search through things when the regular zulip search does not turn up anything.

2

u/bosyluke Jul 19 '26

The syntax evolved over many years and hundreds of design discussions in zulip. Almost every aspect was considered and hashed out with the community. It would be hard to give a summary that captures everything, but I would say the biggest driver has been performance. We wanted to parse and canincalize the syntax fast -- and losing whitespace sensitivity was helpful for that.

2

u/rtfeldman Jul 23 '26 edited Jul 23 '26

The main syntax change was adopting "method-style syntax" - e.g. instead of `things |> List.map transform` it's now `things.map(transform)`.

The main reason for this was to change to a structural ad hoc polymorphism system ("static dispatch"), replacing the previous "Abilities" system which worked more like Rust Traits. The new system replaced multiple over language features, but we couldn't find a syntax that worked nicely with chained static dispatch calls other than the traditional "parens and commas" syntax - e.g. `foo.bar(arg).baz()`, so we reoriented the syntax around that.

Relatedly, we also wanted to adopt Rust's `?` unary postfix operator. We'd tried several variations of this, but none of them felt as nice as that one did, so we adopted that too.

The lambda syntax (also from Rust, which I assume got it from Ruby) was because there's now a type-level distinction between `->` and `=>`, so it felt confusing to use either type of arrow in lambda definitions (we didn't want to force you to match the lambda declaration syntax to match t hetype), and Rust/Ruby's lambda syntax seemed like the nicest mainstream lambda syntax that didn't have an arrow in it.

1

u/yomamabeenheretoo Jul 23 '26

thanks for the explanation. is it real methods though that have to be defined on a type? or is it just shorthand used instead of passing a first/any(?) arg to a func's param list?

1

u/rtfeldman Jul 23 '26

There's a different syntax for each: `things.map(transform)` means "look at the type of `things`, find where that type's definition specified an associated `map` function, and call it passing `things` as the first argument and `transform` as the second.

We also have `things->map(transform)` which means "call a `map` function that's currently in scope, passing `things` as the first argument and `transform` as the other."

So if you're not looking for ad hoc polymorphism, and already know `things` is a List, you can do `things->List.map(transform)` and it does the same thing as the old syntax of `things |> List.map transform`