Roc – A fast, friendly, functional language(roc-lang.org) |
Roc – A fast, friendly, functional language(roc-lang.org) |
Is it a Haskell/Elm thing I’m unfamiliar with?
Is there an (C) FFI planned?
1. Why another language and not a better runtime for an existing language with an install base that already exists?
Feldman is such a charming guy! I highly recommend checking out his podcast Software Unscripted and watching his many talks on YouTube.
The recent SU episode with Brian Carroll talking about WASM in Roc was a great listen.
Roc also has an active community on zulip, so consider stopping by :)
[1] https://twitter.com/sw_unscripted
[2] https://www.youtube.com/results?search_query=richard+feldman
[1] https://taylor.town/elm-2023
Evan also announced some stuff about Elm and Postgres at Strange Loop earlier this year, so I expect another wave of movement soon
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).
> 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...
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.
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!
How do you plan on supporting meta-programming, then? Code-generation as a first class citizen a la .NET?
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.
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.
I usually think of writing explicit type annotations as 'pinning' the type in situations where things are inferred/generic by default.
The whole signature or just the parameters? I thought typescript is pretty chill about inferring the return type on its own.
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.
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.
I like Roc's approach of detecting errors statically but still trying to let you run the code. If a snippet is work in progress and has an unused variable, Go or Zig will refuse to compile it. Yes, an unused variable indicates a problem, which is why it's not going to pass any sensible CI setup and make its way into production, but that doesn't mean I should be disallowed from checking whether what I've got so far works. Roc allows¹ running a program with type errors as long as you don't execute a code path affected by them, which I imagine is very useful for refactoring.
The platform approach is also interesting, but I don't know how it will play into code reuse. I guess the different io/platform interfaces might not be quite as big of a problem in a pure functional language? I'm not experienced enough to tell.
¹: I haven't checked how successful it is, given it's immaturity I expect there to be issues
But, that website has one of the smoothest on boarding experience I've ever seen for a new language. From the inline REPL (with built in tutorial), to the code definition section, its insanely practical. Every new (& old) language should have a website and onboarding experience like this one.
For web UI, I always thought QisKit set the bar pretty high. It's intuitive and informative:
But other than that great to have a quite good idea of the language in just a few seconds.
# Function Definition:
addAndStringify = \num1, num2 ->
Num.toStr (num1 + num2)
# String Interpolation:
"\(greeting) there, \(audience)!"
# desugars to
Str.concat greeting (Str.concat " there, " (Str.concat audience "!"))
https://www.roc-lang.org/tutorialThe unique selling point of Roc is clever optimization to convert purely functional source code to deep-imperative fast machine code, while keeping all the correctness of functional algorithms.
See this video of Richard Feldman for details — «Outperforming Imperative with Pure Functional Languages»: https://www.youtube.com/watch?v=vzfy4EKwG_Y
Among those clever optimizations:
- static reference counting (no GC, like in Rust, but with no borrowing and borrow problems);
- stack allocation of the data with no external links;
- hidden («opportunistic») mutability even in the cases, where Haskell or Lisp will copy the value.
edit:markup
Elm really broke my heart, I believed it will go places it never went. Roc honestly, I am OK to play around with it and build whatever makes me happy.
Edit: related to state management, the "platform" concept looks interesting too https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#...
1. https://www.erlang.org/doc/man/gen_server.html 2. https://hexdocs.pm/elixir/1.12/GenServer.html
I know in my linked [0] that Feldman has since apologized, if only because the comment was being linked to so often [1], but again, why not use any other language where the creators are not so hostile, some even going so far as to say that they "wouldn't trust anything that Richard Feldman was involved in. He was instrumental in making the Elm community a hostile and unwelcoming place."?
[0] https://github.com/gdotdesign/elm-github-install/issues/62#i... (check the edit history)
[1] https://hn.algolia.com/?dateRange=all&page=0&prefix=true&que...
A video about the design decisions of the (hash) map: https://www.youtube.com/watch?v=Z3UGuaJWbaA
Need less Altman, and more altlang posts.
As a big elm fan who does backend work, I’ve been looking Roc for a while with a lot of excitement.
https://github.com/roc-lang/roc/blob/main/FAQ.md
I don't fully agree with all of the reasoning, but it's a reasonable position to stake.
I do suspect there's a way to solve the issues they raise with currying, but haven't thought about it enough to be sure.
The only other complaint I have that isn't addressed in the FAQ is the choice to use '\' to start a lambda. I've never liked that syntax anytime I've seen it. '->' as a infix operator is sufficient to disambiguate, so given all of the other good ergonomic choices they've made, that just kinda sticks in my craw.
I like the 'be fast' and 'be haskell like' approach.
I see you have an Earthfile in the repo. Let me know if you have any Earthly feedback or if I can help with the build in any way, @rtfeldman.
( We've been working on making Earthly faster for Rust builds by using cache mounts. )
Counting all the java operators and keywords, you get 84. This doesn't include assignment operators like "+=" or "-=" (11 such operators).
ChatGPT tells me that python has 36 keywords and 28 operators (not including the 13 assignment operators). This seems low and may be missing some syntactical sugar operators, but even then 64 is a far lower number than F#'s 150. Much debate could be had about which of these operators are fair to count or not, but it seems preliminarily that the data supports the position that functional programming languages (or at least F#) tend to go heavy on special keywords and operators
I do agree that default convention may rely heavily on a few syntaxes that aren't as common in procedural or OO languages.
So instead of:
["a", "b", "c"] |> List.append "d" |> List.append "e" |> List.append "f"
You could have:
["a", "b", "c"] |> append "d" |> append "e" |> append "f"
Since Roc knows that the type returned from each function is a List.
An important note is that any module could expose an append function that has the same interfaces as `List.append`, so that could easily lead to confusion.
» 1/0
1000000000000000000 : Frac *Thanks for pointing it out!
Just installing and trying to reach a valid hello world going just by the errors and it's actively rude within three error messages.
Also it's missing the final newline
Can you share those error messages? I am sure that is not the intent.
What I find surprising is how few programmers I talk to are aware of this, let alone use it. I find it a significant productivity boost.
Extrapolating away from debuggers: Everything should be warning, nothing should be an error. Then adopt a policy that you don't check in warnings. I find it utterly insane that 'unused variable' is treated as an _error_ (in the sense that it prevents compilation). It.. just isn't.
I hear _lots_ of noise in the line of 'well but my dev team will just ignore that rule', but that's a "doctor it hurts when I press here" issue. You don't solve that by just being more beliggerent, you fix that by having a chat with the team.
I wonder what 'friendly' means in the context of 'a programming language', but if its: "Assuming you are not a complete idiot", that's a plus, I guess.
>What I find surprising is how few programmers I talk to are aware of this, let alone use it. I find it a significant productivity boost.
Forward to the past, as often is the case.
See:
https://news.ycombinator.com/item?id=37841588
and its parent and child comment.
All functional language compilers, interpreters, and/or runtimes ultimately have to do this by definition. The efficiency of transpilation varies widely.
The only «sine qua non» optimization through opportunistic mutablity is AFAIK tail call optimization. But it is probably too well-known to call it «clever optimization» in 2023.
But, for example, applying a function to a list will produce the code allocating new list in, say, OCaml and Haskell, at least by default. And Roc will produce the code for mutating the list in-place _from the source code with the same semantic_ (see example for the Quicksort algorithm in the video above).
Compile-time lifetime analysis (that probably is not needed at all in functional languages with GC) and widely used unboxed values are way not common in functional language implementations. For example, in OCaml those features are still experimental (and probably never will be used by default, as in Roc).
- side effects are strictly relegated to async effects, eg all I/O calls return a future
- declarative static loading as imports
Does this mean that it's somewhat equivalent to Rust with everything behind a `Rc` or `Arc`?
But Roc counts references in _compile_ time. So it's like _usual_ (not wrapped in Rc<>) values in Rust. But in Rust the value is deleted from the heap when the stack frame with the _only_ link to it («the owner») is deleted. And in Roc the value is deleted from the heap, when the _last_ link to it leaves the stack.
So we have the machine code _almost_ as efficient as a Rust-produced machine code, but the source code with a much simpler semantics.
Everything is hidden behind some kind of "let's get started!" wizard. https://dotnet.microsoft.com/en-us/learn/languages/fsharp-he...
https://learn.microsoft.com/dotnet/fsharp/get-started/get-st...
What I like about this page is that it shows a basic project structure.
https://fsharp.org/ is the best place to actually start, although i'm guessing you did and went to the hello world which leads back to ms's nightmares.
https://fsharpforfunandprofit.com/ is the standard recommendation from there but there's finally some good youtube and other content out there.
They have a browser repl as well https://try.fsharp.org/
I'm sorry, but what do you mean by that? Roc is "as" pure as Haskell (or Koka), if that's your point.
5 years ago I was upset and posted a comment that was unfairly harsh to another commenter. I apologized at the time, and I meant it. I definitely should not have made the harsh comment that I did. It was wrong. There's no excuse for my having written it.
There are a lot of people working on Roc other than me. I'm not even the top committer anymore. [0] I hope you can find it in your heart to give their work some consideration, if not mine.
As a manager, I empathize with the frustration you were feeling. Steering a team or a community towards a vision is very hard. Just when you think you’re getting somewhere, someone does the exact opposite and mixes people up. It’s easy to lose patience. I’ve made similar mistakes on popular projects and was fortunate enough that no one publicized them. I was allowed to learn from my mistakes without being punished for it.
I don’t have any advice on this. It’ll probably turn out fine.
> EDIT 5 years later: You can see in the edit history of this comment what I originally wrote here; I was upset and said unkind things that I regret, and which nobody deserved to hear. I apologized at the time and I still feel I should apologize again, unequivocally. I was in the wrong here.
Seriously, tho, that's the comment that "made the Elm community a hostile and unwelcoming place" and is still being dredged up after 5 years? That comment can barely even be considered harsh, and is nowhere near hostile.
Is it really worth tearing down someone's life, monitoring the internet for any time their name appears, just so you can spread the continue to spread the hate toward him after so long? This is where you make your stand?
> Threatening a person with exclusion from a community for attempting to patch the source code is quite antithetical to the spirit of Open Source, as far as I can see.
Elm also seemed very promising in the beginning, and honestly I don't even think that comment is so abhorrent on its own. I think Elm died the death of a thousand cuts. If it had only been one errant comment somewhere, it would've been mostly forgotten about by now. Instead, it's Elm that's mostly forgotten about.
So I say best of luck, but also... No thanks for now.
edit: Just so it's completely clear, I am actually implying that "maintainers being dicks" was actually not the problem with Elm. I think people just got especially infuriated by it because they were sick of trying to deal with Elm's breaking changes, of which this represented one. I remember going through and learning Elm and like literally months later everything was completely different and I no longer knew how to make a basic hello world application (around 0.16 or 0.17 maybe? Can't recall. I just remember that effects had changed a fair bit.) I know that to some degree this is the nature of a 0.x product, but at some point it's like "OK... then who is supposed to even use this?" Among other issues of course.
I don't understand why people have to make technical stuff personal.
- simplicity
- strict evaluation model
- devx
- faster runtime
- built in effect system
I love haskell and write it every day, but I have the feeling that the language is probably too complex to really cross over to the mainstream. I remain convinced that the advantages of a pure functional approach are so compelling that we will one day see roc or something like it be the default choice for most programming tasks, and am really excited to see so much progress being made on the language.For example: https://github.com/fsharp/fslang-suggestions/issues/243#issu...
ETA: Also if I'm reading this right Roc appears to natively support some kind of row polymorphism. That's a nice-to-have.
Many other things are features or bugs depending of your preferences. For me, for example, eager evaluation is a big improvement, but YMMV.
Does it have any support for laziness? E.g. could one define the list of all fibonacci numbers similarly to Haskell's
fib = let f a b = a : f b (a+b) in f 0 1If anything, Haskell gets a lot of eyerolls for its slow moving pace and for trying to build a language from mathematical first principles.
It's not perfect (eg.: Monad was not designed to be a special case of Applicative in the beginning, I believe) but it's better at "avoiding baggage" than many other languages I know of.
I also recommend watching it, though the conclusion I came away with is that Evan is kind of in a "next steps" crisis.
Having written inferring compilers and used C++ extensively I appreciate the workings, but I can also see people getting stuck with it.
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!
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.
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?
> 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.
Haskell (55 + some more, because of the grouping): https://wiki.haskell.org/Keywords
F# https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
OCaml: https://v2.ocaml.org/manual/lex.html#sss:keywords
Python: https://github.com/python/cpython/blob/3.12/Lib/keyword.py
Java (I think these are the current ones): https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_k...
Did I?
Considering how much more approachable this Roc documentation/naming is, I'm wondering if it isn't so that the people writing Wikipedia's math pages are probably the same people that are drawn to and writes Haskell/monad "tutorials".
Imagine me not using Linux because of Linus being harsh (yet educative) to some people... or not using OpenBSD because of Theo... or not using Common Lisp because of #lisp... :P I have received some hostile feedback personally, but they were in the right. I did not take it to heart but I learned from it.
That said, I have checked the edit history and I cannot see what the fuss is about. Welp. Moving on.
Forget about all of the drama, imagine if you used Linux and it stopped updating at 2.6. Elm has been at 0.19 since 2018, and that's not because there's nothing left to improve on.
Alexis King, who added these primitives to GHC, on (delimited) continuations https://www.youtube.com/watch?v=TE48LsgVlIU
fwiw we also have fast effect systems in Haskell these days that are more like fancy type sugar on top of the ReaderT over IO style of things (effectful seems to be the most popular).
I know about effectful, but that doesn't use Reader (but provides one) but more or less directly IO (Ref) and "evidence passing", that's why it is faster than the other ones, the drawback is not being able to use non-deterministic effects and noo such thing as coroutines. But I talked about eff ("native" delimited continuations) should be more or less the same, maybe a bit faster, than effectful, but enable non-determinism and coroutines.
yes, that is also important to mention
Note that they were never prevented from forking the project (how would you even do that), instead they chose to try and stronghand the project into accepting their view, which is also not healthy for OSS. Maybe their Elm fork would be mainstream by now if it really catered to developers’ needs.
It's always the same story, too. Someone felt personally wronged by something actually quite minor like their their PR getting ignored/rejected with perhaps a tone too snappy for them, and now they have a personal vendetta until the end of time with no rock nor HN comment section left unturned from them lingering in the past.
Sometimes you need to leave the theater and let the rest of us enjoy the show.
Hopefully a mod sinks this entire thread so we can read interesting thoughts about Roc.
Seems like the Author of Roc is cool now, that was 5 years ago and hasn't done the thing you fear he might do? people get testy, say things they regret.
I understand trust is earned, but it's been 5 years. and the Roc community thus far have been really nice, welcoming and collaborative. I get Elixir and Ruby community vibes from these contributors.
Pick your battles I guess?
Roc isn't Elm. RF is one person in that community, and he said something he regretted 5 years ago and has since not repeated that mistake.
Do you know the moral dealings of every developer of every piece of technology you use?
When it comes to Roc and It's community..ask yourself.
“Am I sure that what I am going to say is true?”
“Is what I'm going to say a good thing?”,
and “Do I really need to say it and is it useful?”
Is that comment from 5 years ago really the most important thing about the Roc Programming language, that anyone reading the comments need to know? Is RF the reason you walked away from Elm?Example: A linked list.
Nitpick: Rc and Arc are not traits.
See also Anton Felix Lorenzen's master thesis: https://antonlorenzen.de/master_thesis_perceus_borrowing.pdf
Mr. Feldman also mentioned some «alias analysis» library «Morphic Solver» from Berkley University, but I cannot find anything about it.
BTW compile time reference counting was already discussed on HN, for example here: https://news.ycombinator.com/item?id=19567666
> Example: A linked list.
IIUC recursive data structures like linked list and trees are not the problem for the static reference counting. Mutation (and, therefore, link cycles) _is_ a problem, but Roc is a clean language with no explicit mutation. =)
> I can see so many basic cases in which it doesn't work
Sometimes runtime reference counting is still used. But, of course, in way more complex situations than just processing a linked list.
> Nitpick: Rc and Arc are not traits.
Of course, my fault. I haven't written on Rust for a long time (and never used this language in a serious project).
edit: link added
It's an another experimental programming language with the static reference counting. «Morphic uses a borrow-based reference counting scheme which is able to eliminate almost all reference count increments and decrements for a large class of programs»ⓒMorphic developer team.
I think I've read a post by Niko Matskakis (Rust lead) who suggested doing something similar. That would be exciting to see!
Yup, that definitely would be an issue.
> When it comes to Roc and It's community..ask yourself.
Sorry, but this kind of faux niceness is precisely what stopped people from asking about issues, as it was always argued that asking about such issues was not "useful," after some time. So yes, I do feel the need to bring up this topic if only for others to evaluate the creators themselves rather than only have "good" things to say that "really need" to be said.