Hacker News new | ask | show | jobs
by davidatbu 944 days ago
I'm super keen to see how Roc pans out, because it sits at an (IMO) riveting spot in the space of PL design tradeoffs:

1. The typesystem will be sound, ML-like, and so simple that any code that doesn't interact with external data will not need _any_ type annotations.

2. An aim to make it the fastest managed compiled lang around (faster than golang).

3. Functional.

4. A focus on fast compile times from the beginning (like golang).

5. Serde from rust is essentially a language builtin.

6. Zero side effects, only managed effects (which I think will do wonders for testability and mocking in a compiled language).

What I'm unclear about is:

1. Whether they'll support macros,

2. Whether their decision to build a whole new IDE will take away from the work that will go into an LSP (it will take a lot to pry away neovim from my hands).

It'd be dope if anyone more familiar can comment on the above!

Also, as feedback to Richard Feldman, your podcast is (imo) great marketing for your lang! It's what's made me excited about your PL.

EDIT: Forgot another feature I'm allured by: ability to run programs with type errors (as best as one can).

5 comments

Glad you've been enjoying Software Unscripted, thank you for the kind words!

> 1. Whether they'll support macros,

The plan is not to support macros. A major reason is that macros tend to conflict with editor tooling, and I definitely have big plans for Roc's editor tooling!

> 2. Whether their decision to build a whole new IDE will take away from the work that will go into an LSP (it will take a lot to pry away neovim from my hands).

The IDE project has been deprioritized a lot (e.g. I don't expect any work to be done on it in 2024) because we realized there's a way to build the editor plugin ecosystem we want to build in a way that works across multiple editors.

There already is a preliminary language server, and there are instructions in the VS Code extension for how to get it set up [0]. I assume something similar should work for neovim!

EDIT: I just noticed that while I was typing this, the author of the Roc language server responded too...hi, Ayaz! Thanks for all your excellent contributions to Roc!

https://github.com/ivan-demchenko/roc-vscode-unofficial#conf...

> The plan is not to support macros. A major reason is that macros tend to conflict with editor tooling, and I definitely have big plans for Roc's editor tooling!

I disagree! I've been working on a macro-heavy Rust project with both proc and declarative macros and the tooling makes them better. I'll add that there is immense value in turning repetitive code into "spreadsheet" style tables of data as well as being able to combine const expressions to get user controllable compile-time errors.

A common example of a tooling problem I've run into with Rust macros is that if I use a string interpolation macro (e.g. in an argument to println!, format!, or dbg!) a lot of VS Code tooling that works outside of macros stops working.

For example, I can't use normal context menu things on an interpolated variable name, like Go To Definition or Refactor. Similarly, if I do a semantic rename of a variable used in interpolation, it doesn't get renamed. Things like this.

Maybe these are solvable problems, but we're talking about widely used macros that have been in the standard library for many years, and even those don't have basic support for normal operations that Just Work in a non-macro context, in one of the most popular Rust editors!

> if I use a string interpolation macro (e.g. in an argument to println!, format!, or dbg!) a lot of VS Code tooling that works outside of macros stops working

I imagine this isn't that hard of a problem to solve (though maybe relying on VSCode to handle renames is part of the issue), but low-enough on the annoyance scale that nobody cares enough to implement it. I'm not going to argue there aren't annoyances with macros and that they're harder for tooling to deal with, but I don't think that's a sufficient justification to not have them at all IMO.

> The plan is not to support macros.

Gotcha. In every lang I've used a lot, I've found meta-programming (compile-time or dynamic) to be valuable (and often indespensible). I can imagine that a lang like Elm that is domain-specific can do fine without the expressive power of macros, but I struggle to imagine that for a general-purpose lang like Roc.

I'm sure you've ruminated on this, so I'm excited to see how it all pans out.

Maybe a Software Unscripted episode with someone who's written a lot of macros is in order? :) David Tolnay (serde maintainer) would be great!

> The plan is not to support macros

How do you plan on supporting meta-programming, then? Code-generation as a first class citizen a la .NET?

+1 for Software Unscripted! I know barely anything about compilers, but I really enjoy being a fly on the wall of these conversations :)
> 1. The typesystem will be sound, ML-like, and so simple that any code that doesn't interact with external data will not need _any_ type annotations.

I've tried using languages with this promise, such as Haskell, and also spent a lot of time with TypeScript, which makes a different set of tradeoffs, and I feel like I've spent enough time on both to know this is the wrong tradeoff to make. It sounds flashy to be able to say that no type annotations are necessary, but in practice what it ends up meaning is that you end up tracking down errors in the wrong parts of your code because the compiler can't figure out how to reconcile problems.

e.g., you have function A incorrectly call function B. How does the compiler know if A has the wrong arguments, or B has the wrong signature? It can't! I know that's a toy example, but it really does lead to a lot of real-world frustration. Sometimes the type errors are very far away from where the actual issues are, and it can lead to a lot of frustration and wasted time.

The TS approach of "please at least annotate all your function signatures" isn't nearly as flashy, but it strikes a much better utilitarian balance.

Oh I totally agree that it's a good idea to annotate top-level functions even if you don't have to, and better compiler error messages is one of the benefits of doing that. Personally I basically always choose to annotate them except in the very specific situation of writing beginner introductory materials, when it's not a given that the reader actually knows how to read the annotations yet.

One of the practical benefits of having full inference is that these signatures can be inferred and then correctly generated by an editor. Like I can write the implementation of my function, and then tell my editor to generate a type annotation, and it can always generate a correct annotation.

That saves me time whenever I'm writing the implementation first (I often write the annotation first, but not always), even if I end up wanting to massage the generated annotation stylistically. And unlike having Copilot generate an annotation, the type inference system knows the actual correct type and doesn't hallucinate.

To me, the main benefits of type inference at the top level are that they offer beginners a more gradual introduction to the type system, and that they offer experts a way to save time through tooling.

> Personally I basically always choose to annotate them except in the very specific situation of writing beginner introductory materials, when it's not a given that the reader actually knows how to read the annotations yet.

Having just gone through your tutorial (albeit not yet with a computer in hand, that's the next step), might I suggest putting those annotations in anyway? I suspect most of the people reading the tutorial will already be programmers, and one of the most useful things I've found when reading tutorials and guides is when the code examples look as much like real code as possible. When I'm reading that initial documentation, I'm not just trying to figure out what's different about this language from other languages, but I also want a sense of what it looks like to actually read and write idiomatic code in that language - what sort of formatting conventions are there, what do variable or type names typically look like, are there any common idioms, etc. Ultimately, my goal is to get up to speed and begin writing productive code as quickly as possible, so seeing type annotations everywhere is a sign to me that type annotations are good practice and something to get used to.

In general, I found the tutorial a bit too aimed towards someone learning their first programming language, which is a demographic that I suspect are unlikely to be using Roc any time soon! Even if they are a demographic you're targeting, I wonder if they'd be better served by a separate explicit "Roc as a first programming language" document that goes through the basics. Then in the main document, do some repl stuff at the beginning, but move quickly on to what regular development work might look like - starting a new project, writing functions with types, using tasks/effects, adding tests, dependencies, etc.

With all that criticism out of the way (sorry!) I do want to say that I love pretty much everything that I've seen so far, especially the focus on practical usage. It's great to see an example CLI and an example web server right on the home page - I feel like these are often left as complete afterthoughts for these sorts of languages, but they're the sort of real-world programs that dominate software in the industry.

I'm also really excited to have a play around with the effects/tasks system. It looks really powerful, but not too complicated to actually use as a base abstraction.

And I agree that having powerful type interface can be a great tool, even if you supplement it with type annotations for the sake of explicitness. Is there an explicit type hole mechanism as well, for getting the compiler to spit out the types it expects?

Thanks for the kind words, and thanks for the feedback!

> Is there an explicit type hole mechanism as well, for getting the compiler to spit out the types it expects?

Not currently, although you can write `_` for any part of a type annotation that you don't want to bother annotating (which means that part of the type will be inferred as if you hadn't written any annotation for it), and we either have or want to have "hover to see the type" in editor extensions.

The Haskell approach is "annotate all top level declarations" (even if not exported) and OCaml has module signatures. But both (and Roc) don't make up new "types" like Typescript does.
I think a general 'explicit > implicit' priority is good enough to cover most cases - a written signature takes precedence over an inferred one. The compiler can also simply emit errors at both sites and let the writer figure it out.

I usually think of writing explicit type annotations as 'pinning' the type in situations where things are inferred/generic by default.

> The TS approach of "please at least annotate all your function signatures"

The whole signature or just the parameters? I thought typescript is pretty chill about inferring the return type on its own.

It usually is as long as you don't do anything recursive (and a few select polymorphic instances), I can see people getting bitten by it.

Having written inferring compilers and used C++ extensively I appreciate the workings, but I can also see people getting stuck with it.

Rust takes the "at least annotate all your function signatures" approach as well. It's essential for making borrow-checking tractable (for both the compiler and the programmer).
Rust has the "you must annotate your functions signatures, because there is no global type inference possible" approach.
It's a carefully chosen, deliberate design decision [1]. Interface boundaries should be stable.

[1] https://steveklabnik.com/writing/rusts-golden-rule

It's unlikely that macros will be supported. Regarding editors, it's unlikely that effort on the advertised Roc editor will start in earnest some time soon. I actually recently merged an LSP implementation into the mainline compiler ([details on how to integrate here](https://roc.zulipchat.com/#narrow/stream/304902-show-and-tel...)), and that's likely to develop more in the near future, before a standalone Roc editor is available.
An aim to make it the fastest managed compiled lang around (faster than golang).

I find this an interesting perspective: That Golang is a compiled managed language. This is certainly correct. I don't see this being expressed to often, however.

Seems to be a language equivalent to a subset of strict Haskell.

There doesn't seem to be any particularly compelling reason to use it, and note that, unless they can use libraries from an existing language, there needs to be a really MAJOR reason to use a new language to compensate the lack of libraries.