Stream: beginners

Topic: my from time to time feedback about Roc


view this post on Zulip Artur Swiderski (Nov 18 2023 at 16:00):

Since I am using Roc for quite some time I would like share my opinion about pros and cons of the language

pros -> in general this language has simplicity build in, using only handful of abstraction I am able to do a lot(this alone is insane in comparison to C++ for example) . I like structure of those programs just redirecting data from one place to another via functions.
It goes deep, having my day to day work and programming from time to time is difficult I have only so much of my mental resources to spare on this. In C++ (and other languages I tried including python, haskell, even lua ) but especially C++ burnout of my "daily mental resources" is immense. I just can't effort to spend so much of my daily focus and at the same time make next to zero progress. Roc stands out in that regard I am wasted to but I see some progress. This ratio of effort-> result I am getting is acceptable. It is deal breaker for me. I can still hold my live balance in a grip. That's the reason I haven't moved to next thing and I plan to stay, provided that you do not spoil design by unnecessary levels of complexity/weird magic/ to much syntactic sugar.

cons ->
lack of multiline comment (kind of annoying )
crashing dbg (very annoying)
general immaturity (I feel it all the time)
lack of multithreading (this I plan to use in the future)
no bit and byte access (I think sometime it is useful to be able to access float/integers byte by byte or bits, especially during some low level stuff tweaking )

other remarks:
overall I don't get how application is created which is kind of obvious when I am using C or C++ this may be meaningful if I at some point decide to create application on microcontroller (bare metal). Somehow I don't get how those abstraction are translated to real code. How I will access specific address etc.

there are some things in language itself, which makes working with it manually tedious at times ("when.. is" block all over the place).
In that regard I consider it to be my homework, to just get used to it. I would not expect improvements necessarily. Good outcome is that those constructions are forcing me to think about things I usually neglect, so it may be good thing overall, but still uncomfortable and time consuming

view this post on Zulip Richard Feldman (Nov 18 2023 at 17:25):

thanks for the feedback! It's been really cool to see the things you've been working on :smiley:

no bit and byte access (I think sometime it is useful to be able to access float/integers byte by byte or bits, especially during some low level stuff tweaking )

I'd like to do this, although there are some design questions because endianness makes doing it a tricky to be both performant and also consistent across targets

view this post on Zulip Brendan Hansknecht (Nov 18 2023 at 17:49):

To be fair on the endianness, it may be reasonable to just start by not caring. Swapping from little to/from big endian has to be builtin and fast in processors cause processors are almost always little endian and network protocols are big endian. We probably could just make both functions.

The other important factor is we have tuples which should allow this all to be done in a cpu register instead of requiring allocating a list.

view this post on Zulip Richard Feldman (Nov 18 2023 at 17:56):

another design question is what number types should support it.

ints seem fine, but exposing the internal structure of Dec would make changing that structure in the future a breaking change.

also for floats I'm not sure if the NaN flags are consistent enough across CPUs that they'd actually give the same answer regardless of target

view this post on Zulip Richard Feldman (Nov 18 2023 at 17:58):

but I guess figuring these things out is necessary in order to support fast Encode implementations that serialize to binary formats

view this post on Zulip Anton (Nov 18 2023 at 19:25):

crashing dbg (very annoying)

We have a pull request in the works that should massively improve the reliability of dbg.

view this post on Zulip Anton (Nov 18 2023 at 19:28):

I don't get how application is created

It sounds like it would be good to have a page on the website that explains this with a nice diagram. I'm already going to make an issue for it, but others are welcome to suggest alternative options.

view this post on Zulip Brian Carroll (Nov 18 2023 at 19:43):

We have some diagrams here https://github.com/roc-lang/roc/blob/main/crates/README.md

view this post on Zulip Luke Boswell (Nov 18 2023 at 20:08):

@Brendan Hansknecht s diagrams from that latest stream were the best I've seen for the app/platform/host breakdown. Could you share them? Maybe we can massage into a guide or page for the website?

view this post on Zulip Brendan Hansknecht (Nov 18 2023 at 20:40):

If we want to refactor these for the site, I have the original excalidraw file. Can make it all in one line, change colors, export in both light and dark mode. This is the version I showed on stream as a png:
Roc-Platform-to-Final-Binary-Dark.png

view this post on Zulip Richard Feldman (Nov 18 2023 at 21:00):

@Brendan Hansknecht that looks great!!! Could you do it in an inline SVG with classes for colors so we can switch its color scheme in CSS for light vs dark mode? :smiley:

view this post on Zulip Brendan Hansknecht (Nov 18 2023 at 21:05):

I can try. Definitely can export as SVG. Don't really know how classes work in SVG for color.

view this post on Zulip Richard Feldman (Nov 18 2023 at 21:05):

eh I can make that change, it's easy

view this post on Zulip Richard Feldman (Nov 18 2023 at 21:05):

I dunno if it's possible to make it include the text as actual text instead of SVG shapes, but that would be best if possible somehow!

view this post on Zulip Artur Swiderski (Nov 19 2023 at 01:45):

Looking at diagrams it seems I have to find platform library for given architecture and link Roc application with it? Roc wrapper has to be available ? One other thing all 3rd C libraries has to be in this platform library ? I do not need this functionality right now but I have some ideas and it may be useful at some point

view this post on Zulip Brendan Hansknecht (Nov 19 2023 at 01:52):

Given rocs design, the platform is essentially the runtime of a roc application and decides all of the primitives that it has access to. Currently there aren't that many platforms and custom ones would often be a requirement.

This does limit the roc ecosystem. It is not easy to just pick an arbitrary library in c and depend on it in a roc app. That library would have to be exposed to roc through a platform. As such, roc kinda depends on platform developers building custom experience for roc users (kinda like how elm gives a custom web UI experience, but is otherwise quite limited in functionality).

Note, through tools like libffi, a platform could give a roc application dynamic access to code in any shared library, but I assume this would be pretty uncommon.

view this post on Zulip Brendan Hansknecht (Nov 19 2023 at 01:53):

If you wanted to depend on an arbitrary rust library in a basic CLI app for example, the simplest solution would be to fork the platform. Add the new library and wrapping effects for each function you want to use and then use that instead of the original basic CLI platform.

view this post on Zulip Eli Dowling (Nov 19 2023 at 02:38):

What would roc's fesability be for running in very constrained platforms? Say I wanted to run on a microcontroller? Could I write a platform that would run on a riscv esp chip? Or even avr on an Arduino?

view this post on Zulip Luke Boswell (Nov 19 2023 at 02:41):

@Brendan Hansknecht made a platform https://github.com/bhansconnect/roc-microbit for running on a microbit embedded processor.

view this post on Zulip Luke Boswell (Nov 19 2023 at 02:41):

I think he made a line following robot with it

view this post on Zulip Brendan Hansknecht (Nov 19 2023 at 02:44):

So our current constraint is 32bit systems and requiring some compiler target hacking cause we don't expose everything needed for this. So without messing with the compiler, you are stuck more with like arm 32bit Linux, but with very minor target hacking any arm or riscv 32bit microcontroller should be a possible target

view this post on Zulip Eli Dowling (Nov 19 2023 at 08:32):

Oooo cool, thanks. That's good to hear that it's feasible.
Currently the best language I've found for doing embedded development is nim.... But nim has... Problems. I feel like of all people y'all are probably pretty likely to know a bit about a high-level high performance language that does reference counting.

I like many things about the nim language, but the design feels extremely scattered and just has the "kitchen sink approach" but it also is majorly lacking in really basic ways. No interfaces, so no ability to do mocking and testing. No good unit testing story at all for that matter. Plus the editor tooling is a mess despite being a mature language. And I mean a mess at the compiler level. I looked at trying to improve it and it felt totally futile as someone who isn't part of the core team.

Anyway, I feel like roc, with a focus on performance, and the whole platforms business, might be a good choice in future for high powered platforms like ESP and STM

Side note, does roc have arrays? I couldn't find any array syntax

view this post on Zulip Brian Carroll (Nov 19 2023 at 08:38):

Eli Dowling said:

Side note, does roc have arrays? I couldn't find any array syntax

Our List is like C++ std::vector or Rust Vec. All elements are allocated side by side in memory. It's not a linked list like most functional languages have.

view this post on Zulip Brian Carroll (Nov 19 2023 at 08:40):

Eli I wasn't sure which parts of your comment were about Roc and which about Nim.

view this post on Zulip Eli Dowling (Nov 19 2023 at 09:29):

Oh oops @Brian Carroll I see how that could be ambiguous, I've edited for clarity. Good catch, thanks :)

view this post on Zulip Eli Dowling (Nov 19 2023 at 09:32):

Brian Carroll said:

Eli Dowling said:

Side note, does roc have arrays? I couldn't find any array syntax

Our List is like C++ std::vector or Rust Vec. All elements are allocated side by side in memory. It's not a linked list like most functional languages have.

Ahh, I see, excellent, thanks :)
Looking forward to when roc has some good docs for the standard library and all the current language features. Still I suppose that makes the journey of discovery more exciting :sweat_smile:

view this post on Zulip Luke Boswell (Nov 19 2023 at 09:39):

If you come across any features that aren't documented very well, please log an issue for it or drop a comment in #contributing channel. I would be interested to try and improve that where I can.

view this post on Zulip Artur Swiderski (Nov 19 2023 at 15:17):

regarding my remark about (when is) block
I make up my mind what I don't like
Sometimes in a function I already determined that let say list is no empty
But logic goes on an when I am using the same array in different context, although in the same data path when it was establish already , that it is not empty. Again I have to use (when is ) block, with possible error -> array empty
I agree first time it make sense but second time ??
List.first could be smart in this case and work without (Ok/Err - as an option if somebody insist it should be still allowed ) compiler could easily figure this out. This may cause some confusion at first but for seasoned programmer, it would be efficient and maybe cleaner in long run

view this post on Zulip Artur Swiderski (Nov 19 2023 at 15:27):

if List.isEmpty lst then
#and here this would be allowed
k = List.first lst
#old school also
when List.first lst is
...

else
..

I agree this may be confusing at first but I see benefits in long run and people aren't stupid after all, they will understand the reasoning behind it

view this post on Zulip Isaac Van Doren (Nov 19 2023 at 15:39):

One way you can get around that is once you confirm that the list is not empty, take the first element and make a (a, List a). Then you have encoded in the type that it is non empty and can explicitly work with the first element.

view this post on Zulip Isaac Van Doren (Nov 19 2023 at 15:40):

The downside is that then you can’t pass the tuple into functions that take a list

view this post on Zulip Anton (Nov 19 2023 at 15:41):

Here is a brief explainer about patterns like this.

view this post on Zulip Isaac Van Doren (Nov 19 2023 at 15:42):

Other languages have libraries that support NonEmpty lists that are basically what I suggested above, but with functions made for them

view this post on Zulip Anton (Nov 19 2023 at 15:43):

Uhu, I believe we've discussed including NonEmptyList in the language before, let me see if I can find it.

view this post on Zulip Isaac Van Doren (Nov 19 2023 at 15:45):

Also, a lot of cases like the one you posted above are solved by pattern matching on the list instead of using an if else. Then you can access the first element directly.

when list is
[first, ..] ->

view this post on Zulip Anton (Nov 19 2023 at 15:52):

Uhu, I believe we've discussed including NonEmptyList in the language before, let me see if I can find it.

old discussion

view this post on Zulip Anton (Nov 19 2023 at 15:59):

I agree first time it make sense but second time ??

I think this could be done with dependent types but that would significantly increase compiler complexity. It could be something we look at in the future, but for now the compiler has enough complex bugs without using dependent types :p

view this post on Zulip Artur Swiderski (Nov 19 2023 at 16:01):

ok I got that there are some ways to propagate information or distinguish via pattern matching,

I will see how it improves things over time thx for tips

view this post on Zulip Brendan Hansknecht (Nov 19 2023 at 16:40):

A quick comment. Often times calling List.isEmpty is the issue. You don't really want to call it if you are also going to call something like List.last. instead, you should just call List.last where you would have called List.isEmoty and use the error case of List.last as your check for whether or not the list is empty. I often find a lot of code that check a list twice can be rewritten to just check it once. Using the errors is the important part

view this post on Zulip Brendan Hansknecht (Nov 19 2023 at 16:42):

Oh also, if you really want, you can make a function that will unwrap a result or crash. Then you can do:

List.last ... |> crashOnError

Given the error case is impossible, this may be fine.

view this post on Zulip Artur Swiderski (Nov 20 2023 at 03:19):

could you show example of this

List.last ... |> crashOnError

view this post on Zulip Artur Swiderski (Nov 20 2023 at 03:19):

I don't get idea

view this post on Zulip Isaac Van Doren (Nov 20 2023 at 03:22):

The idea is

when List.last list is
    Err _ -> crash "empty list"
    Ok last -> doSomething last

although I would say this is a bit of an anti pattern

view this post on Zulip Luke Boswell (Nov 20 2023 at 03:28):

Or maybe this could work?

unwrapOrCrash = \x ->
    when x is
        Err _ -> crash "unwrapping value"
        Ok a -> a

last = List.last list |> unwrapOrCrash

#do something with last

view this post on Zulip Richard Feldman (Nov 20 2023 at 04:33):

that can work, but I specifically don't want to add a function like that to builtins because it makes crashing convenient...and unless you're writing a throwaway script or something (e.g. for Advent of Code) there's usually a more graceful way to handle the error, which I'd prefer to make feel relatively more natural!

view this post on Zulip Luke Boswell (Nov 20 2023 at 05:07):

Yeah definitely really hacky.

The roc type system is so nice, so there are really nice ways to handle errors and have a good program flow, but I'm still getting used to writing things a little differently and discovering these patterns.

view this post on Zulip Brian Carroll (Nov 20 2023 at 10:55):

Here's an article I found really useful when I was learning these patterns (in Elm)
https://thoughtbot.com/blog/problem-solving-with-maybe

view this post on Zulip Artur Swiderski (Nov 20 2023 at 16:17):

Ok this crash approach is something I would go for, I somehow missed this capability although I see now there is chapter in tutorial, I will just define convenient set of functions and this is basically feature I am asking , provided that matching alone will not solve specific case thx

view this post on Zulip Artur Swiderski (Nov 20 2023 at 16:26):

it is out of control of compiler (that's bad) but this communicates the notion that, there has to be value here, anything different is just ridiculous. In such scenario inventing error messages or alternative paths flow is tedious( at least for me)

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 16:44):

To clarify here, with restructuring your code and potentially using result helper methods, there should be a cleaner solution to this.

The crash function I mentioned above is just a simple solution to remove some of the tedium. I would have to analyze specific code more deeply to give examples of how it could be rewritten to take advantage of other roc patterns to clean up the code.

If you share links to specific functions that are bad, I can try and see if I can show off other patterns to help clean them up.

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:02):

Ok I will invent something

k = [2,3,4]
when  List.first k is
      Ok val:
              .....
              #and  here
               I know  list not  empty already->  List.last k
      Err  _->
      ......

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:03):

here maybe I could use pattern [valfirst, .., valLast] but what when k = [4] (first and last still valid)

Other example

if (List.len lst > 5)  && (List.len lst < 10) then
     List.get lst  7   # I know this element is there

things like that sometimes not so easy to match

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:12):

I don't know how real this is but sooner or later there will be something difficult

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:16):

I definitely would prefer links to real functions rather than examples. I think full context is often important for these kinds of suggesttions.

That said, I'll still comment on snippets and see If I can help some.

For the first example, I see two main options:

k = [2,3,4]
when (List.first k, List.last k) is
    (Ok first, Ok last) ->
        ...
    _ ->
        ...

This second option would be to try and use the result related apis.
I am going to do something very direct, but this requires context to see what would fit best in a full function.

res =
    first <- List.first k |> Result.try
    last <- List.last k |> Result.try
    # use first and last. Errors are now grouped and would be handled elsewhere

# handle every single error at the end
when res is
    Ok ... ->
    Err ... ->

view this post on Zulip timotree (Nov 20 2023 at 17:17):

Artur Swiderski said:

if (List.len lst > 5)  && (List.len lst < 10) then
     List.get lst  7   # I know this element is there

things like that sometimes not so easy to match

Here is an option using when:

when List.get lst 7 is
    Ok x if List.len < 10 -> ...
    _ -> ...

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:17):

For the second example, I do agree that you are kinda just stuck unless you can make a larger refactor. So I don't have any immediate suggestions. That would definitely need more context for me to potentially have suggestions.

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:18):

huh, I guess when guards could potentially work. interesting idea @timotree

view this post on Zulip timotree (Nov 20 2023 at 17:19):

Note that you need List.len lst > 7 to know that List.get 7 will succeed, not just List.len lst > 5, so there is a bug in the second example using if

view this post on Zulip timotree (Nov 20 2023 at 17:25):

Is there a way to bind a name to a pattern like @ patterns in Rust? If those existed (let's say with the as keyword), you could do the following for the first example:

when k is
    [first, .., last] | [first as last] -> ...
    _ -> ...

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:30):

This does work....wow, way better:

when [1, 2] is
    [first, .., last] | [first as last] -> Ok (first + last)
     _ -> Err EmptyList

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:31):

what "as" means ?

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:31):

It give another name to a value.

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:32):

So it is making it such that both first and last have the same value if the list is a single element

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 17:32):

kinda equivalent to doing:

when [1, 2] is
    [first] ->
        last = first
        ...

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:33):

ok if I have real live problem I will let you know

view this post on Zulip Artur Swiderski (Nov 20 2023 at 17:50):

things like this I consider horrible btw.

when List.get lst 7 is
    Ok x if List.len < 10 -> ...
    _ -> ...

I don't even understand what is intention here when I see this


when k is
    [first, .., last] | [first as last] -> ...
    _ -> ...

above is acceptable


but below is better

k = [2,3,4]
when (List.first k, List.last k) is
    (Ok first, Ok last) ->
        ...
    _ ->
        ...

and horrible again

res =
    first <- List.first k |> Result.try
    last <- List.last k |> Result.try
    # use first and last. Errors are now grouped and would be handled elsewhere

# handle every single error at the end
when res is
    Ok ... ->
    Err ... ->

My general view
For me code should be very simple in construction (if I don't understand specific line in like 0.3 sec, this line is a failure , I mean in it's structure composition, I still could have not idea why it is there, that's is fine for me )
It is different story with entire application, complexity may lay in program structure because sometimes problem one has to solve isn't necessarily easy, so intricate structure although not welcome it is something I have to live with at times. Although in many cases it happens because of lack of imagination but still there is no way around it(mind limitations)

view this post on Zulip Brendan Hansknecht (Nov 20 2023 at 18:05):

That's totally fair. I think if you use tasks enough, syntaxes like Task.await and Result.try become a lot clearer, but I understand avoiding them. I did that for a very long time.

view this post on Zulip Anton (Nov 20 2023 at 18:08):

I have experienced this problem in the past as well, this made me think of a simplified view (with for example editor plugin) for roc files that hides all uses of Task.await, Result.try, backpassing and general error handling. A lot of the time you don't care about the specifics of the error handling and just want to understand what the code does for the case where everything goes right (no empty lists, no errors...). I have not written down any formal proposal for this but I do see a lot of value in exploring something like it in the future.

view this post on Zulip Notification Bot (Nov 20 2023 at 18:14):

2 messages were moved from this topic to #beginners > wasm32 basic-cli by Anton.

view this post on Zulip Luke Boswell (Nov 20 2023 at 23:01):

This was specifically something I rushed past at the start, and kept bumping into issues trying to do things. Seeing other peoples solutions to the AoC puzzles I was working on was a massive help to see different ways to do the same thing. I think I had to slow down and spend some time playing with Result and Task building different programs to get comfortable with it.


Last updated: Jul 06 2025 at 12:14 UTC