Stream: beginners

Topic: My reaction to Roc, as a beginner


view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:33):

Reading the Roc-for-Elm tutorial. So far most things are reasonable and/or improvements over Elm.

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:34):

The first weird thing I found is the syntax for open records which is just... Weird
{ name : Str, email : Str }a -> { name : Str, email : Str }a especially because I'd feel like writing it as
{ name : Str, email : Str } a -> { name : Str, email : Str } a which feels... even weirder :thinking:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:35):

Qualified-but-not-dotted record update is :chefs-kiss:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:36):

...and the table function will fill in its default values for x and y. There is no need to use a defaultConfig record.

Except it has default for title and description. Eh. Guess a PR is incoming :laughing:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:39):

https://github.com/roc-lang/roc/pull/5184

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:41):

Optional record fields are just Comfortable

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:42):

Destructuring is the only way to implement a record with optional fields. (For example, if you write the expression config.title and title is an optional field, you'll get a compile error.)

I get it but also... it could just be a Maybe? But then you'd bless a specific type which is suboptimal I guess? :thinking:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:43):

when/is instead of case/of

Fair enough

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:43):

pipe for multiple patterns

YES thank you

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:43):

pattern guards

Yes

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:44):

can use if guards to match against constants

YESSSSS

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:44):

So tempted to rewrite nuPlot in Roc now :laughter_tears:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:45):

and Bool is an opaque type. The values Bool.true and Bool.false work like True and False do in Elm.

weeeeird. Is this to avoid when/is on booleans? Guess one would when/is/if then :thinking:

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:46):

Having to qualify booleans like Bool.true and Bool.false feels weeeeeird for such a basic thing. Is the idea to push towards custom types harder?

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:47):

Task type args are flipped

...mmmmmmmmmmh. I get it. But also. Mhhhhhhh

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:48):

combined error tags

AMAZING

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:49):

open tag unions syntax

see my comment about re: open record syntax. weeeeeeeeeeeeeeeeeeeird

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:49):

You don't need to declare tag unions before using them. Instead, you can just write a tag (essentially a variant) anywhere you like, and Roc will infer the type of the union it goes in.

Thank you

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:50):

> y = Foo "hi" Bar
Foo "hi" 5 : [Foo Str [Bar]*]*

This is another typo but I'm not sure what the intent was

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:52):

Similarly to how if you put { name = "" } into elm repl, it will infer a type of { a | name : String } - that is, an open record with an unbound type variable and name : Str field

It doesn't tho. It only infers that open record if you hello {name} = "Hello " ++ name

view this post on Zulip Brendan Hansknecht (Mar 23 2023 at 23:54):

For optional record fields:

I get it but also... it could just be a Maybe? But then you'd bless a specific type which is suboptimal I guess?

This is because optional record fields aren't really optional. They really should be called record fields with default values for a specific function. This is all known and dealt with at compile time. Depending on types, we generate specific functions with those values set as need. If you had a record field that was truly optional at runtime, you would use a Result or Maybe like value to hold the data.

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:56):

Makes sense. I learned from Elm that it's fine to restrict features to nudge users towards better code

view this post on Zulip Leonardo Taglialegne (Mar 23 2023 at 23:57):

Opaque types

:shrug: ok

view this post on Zulip Brendan Hansknecht (Mar 23 2023 at 23:58):

Also, for bool, the context is here: #ideas > opaque bools

view this post on Zulip Brendan Hansknecht (Mar 23 2023 at 23:58):

Doc at the top here: https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/opaque.20bools/near/286971346

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:05):

Yupp, I'm a convert. Love it

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:06):

app vs interface modules

Mmmmh :thinking: wouldn't this prevent multiple entrypoints? Which is a nice thing sometimes

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:07):

@Brendan Hansknecht I appreciate your responses, thanks for taking the time :purple_heart:

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:07):

All imports and exports in Roc are enumerated explicitly; there is no .. syntax.

YES

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:07):

(Later on, we'll talk about how opaque types work in Roc.)

_technically_ you already did

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:08):

Elm does permit overriding open imports - e.g. if you have import Foo exposing (bar), or import Foo exposing (..), you can still define bar = ... in the module. Roc treats this as shadowing and does not allow it.

Good

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:12):

Roc has no << or >> operators, and there are no functions in the standard library for general-purpose pointfree function composition.

:clap:

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:13):

The only "good" use of << is List.filter (not << String.isEmpty)

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:14):

Which. Like. List.filter String.Extra.isNotEmpty

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:15):

Separately, you don't need parens or an operator to pass when or if as arguments. Here's another example:

Nize

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:16):

list =
    num <- List.map numbers

    num + 1

oooooooooh, this is NICE. At first I was like "oh, ok, monadish syntax" but no, this is way better

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:17):

incrementedNumbers =
    num <-
        [1, 2, 3]
            |> List.reverse
            |> List.map

    num + 1

ooooh, it combines So Nicely

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:18):

Oh, _that's_ why you Task.await! It's just an andThen mean to be used in backpassing style!

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:20):

Dec

:clap:

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:22):

If you encounter integer or Dec overflow in Roc, by default you get a runtime exception.

Oh. Oh no. Oh PLEASE no. Nope. Noooope. Like. I get it. But. Ugh

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:27):

No Char. This is by design.

Good

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:30):

dbg auto-yeeted in prod

Good

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 00:32):

crash keyword

mmmmmmmh :thinking: I like that it's uncatchable, but I would also be wary of a library just randomly crashing on me?

view this post on Zulip Brendan Hansknecht (Mar 24 2023 at 00:42):

Leonardo Taglialegne said:

If you encounter integer or Dec overflow in Roc, by default you get a runtime exception.

Oh. Oh no. Oh PLEASE no. Nope. Noooope. Like. I get it. But. Ugh

For reference, this was kinda a fitted compromise that matches the other numeric types. We tried enforcing more checked arithmetic on numeric types, but fundamentally it adds too much friction to the language. So Dec behaves the same as all the other regular number types. When you do a + b, whether it is a U64, I8, or Dec, it may overflow (which will run time panic). You, of course, can opt into checked arithmetic that will return a result.

view this post on Zulip Brendan Hansknecht (Mar 24 2023 at 00:47):

Leonardo Taglialegne said:

crash keyword

mmmmmmmh :thinking: I like that it's uncatchable, but I would also be wary of a library just randomly crashing on me?

There was a lot of debate around this and it may change at some point, but fundamentally crash can be useful in libraries. For example, Dict is written in Roc. It uses crash for states that should be unreachable. Without crash, there is not really a clear thing to do if somehow you reached a state that should be unreachable and would definitely otherwise be a bug. The general thought is that it will hopefully turn out like panic in rust libraries. Most rust libraries will never panic, or if they do it is an unrecoverable library bug that should be filed upstream.

view this post on Zulip Luke Boswell (Mar 24 2023 at 01:00):

Interesting, for some reason I thought crash only worked in app modules.

view this post on Zulip Luke Boswell (Mar 24 2023 at 01:10):

Here is Richard's proposal and zulip thread discussion for anyone who is interested in reading more about crash design.

view this post on Zulip Brendan Hansknecht (Mar 24 2023 at 01:21):

Interesting, for some reason I thought crash only worked in app modules.

Just double checked. I was able to put a crash directly in an interface in a platform.

view this post on Zulip Luke Boswell (Mar 24 2023 at 01:22):

Yeah, I believe you. It is definitely in Dict. I guess I assumed somewhere along the way. Good to know. I think it's a good trade-off.

view this post on Zulip Leonardo Taglialegne (Mar 24 2023 at 01:26):

Anyway. Considering that you're only a novice once I wanted to share my impressions :)

I will now probably try and play with roc a bit

Btw, thanks to all the people who worked on flake.nix, I was able to install roc by just adding it to home-manager like this: https://github.com/miniBill/dotfiles/commit/c0f1775048011f9e0580d842604870109be12a3f

view this post on Zulip Notification Bot (Mar 24 2023 at 22:02):

7 messages were moved from this topic to #ideas > open record syntax by Richard Feldman.

view this post on Zulip Leonardo Taglialegne (Mar 25 2023 at 12:23):

The List module does not expose `length`:

I'd write "The List module does not expose a value or function named length".
The current verbiage sounds like "the function exist but is not exposed".

view this post on Zulip Leonardo Taglialegne (Mar 25 2023 at 12:24):

» -(List.len ['a'])

18446744073709551615 : Nat

... I know why it happens but it's still surprising

view this post on Zulip Leonardo Taglialegne (Mar 25 2023 at 12:24):

» -(List.len ['a']) + 3.0
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Erroneous', crates/compiler/mono/src/ir.rs:10433:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Whoops :D

view this post on Zulip Richard Feldman (Mar 25 2023 at 13:15):

Leonardo Taglialegne said:

» -(List.len ['a'])

18446744073709551615 : Nat

... I know why it happens but it's still surprising

oh this is actually a bug! I've opened https://github.com/roc-lang/roc/issues/5197 for it

view this post on Zulip Richard Feldman (Mar 25 2023 at 13:17):

Leonardo Taglialegne said:

» -(List.len ['a']) + 3.0
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Erroneous', crates/compiler/mono/src/ir.rs:10433:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This is also a bug! Would you mind opening an issue for it?

view this post on Zulip Leonardo Taglialegne (Mar 25 2023 at 14:04):

Richard Feldman said:

oh this is actually a bug! I've opened https://github.com/roc-lang/roc/issues/5197 for it

Would it be possible to instead forbid using negate on Nat?

view this post on Zulip Leonardo Taglialegne (Mar 25 2023 at 14:06):

Richard Feldman said:

This is also a bug! Would you mind opening an issue for it?

Minimized and posted https://github.com/roc-lang/roc/issues/5199

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:17):

Leonardo Taglialegne said:

Richard Feldman said:

oh this is actually a bug! I've opened https://github.com/roc-lang/roc/issues/5197 for it

Would it be possible to instead forbid using negate on Nat?

I considered it a long time ago, but I concluded it wouldn't be worth it because it would introduce an entire type-level distinction between signed and unsigned numbers basically just to prevent calling a function unnecessarily :big_smile:

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:18):

(also there would be a nonzero cost to compile times to introducing that distinction)

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:19):

I could see an argument for having negation of an unsigned integer always crash, even if you give it 0

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:19):

and just say "you tried to negate an unsigned number, and unsigned numbers can't be negative"

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:20):

that would mean that if you ever called Num.neg on an unsigned number, it wouldn't give you a compile-time error, but in practice you'd always find out about it right away if you ever tried it

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:21):

you wouldn't even have the "whoops it was 0 in my test cases, so I didn't notice" edge case

view this post on Zulip Richard Feldman (Mar 25 2023 at 14:22):

actually that seems worth trying and seeing if it causes problems; I've updated the issue

view this post on Zulip Ayaz Hafiz (Mar 25 2023 at 18:10):

Richard Feldman said:

I considered it a long time ago, but I concluded it wouldn't be worth it because it would introduce an entire type-level distinction between signed and unsigned numbers basically just to prevent calling a function unnecessarily :big_smile:

For builtin operations, Roc would not need to introduce a type-level distinction - the compiler already has support we added for checking, for example, does this integer fit in a U8? So, we could apply the same analyses for catching negations of unsigned integers, without having to change the type-level syntax or integer types a Roc programmer needs to know.

The same kind of thing could be used to catch checks like myU8 < 0u8 which will never succeed.

view this post on Zulip Richard Feldman (Mar 25 2023 at 18:40):

hm, what about something like this?

negatesArgInSomeCodePath : Num a -> Num a

x = negatesArgInSomeCodePath someSignedInt

y = negatesArgInSomeCodePath someUnsignedInt

in that case, would it give an error for y? if so, what would the error be? :thinking:

view this post on Zulip Ayaz Hafiz (Mar 25 2023 at 18:45):

yeah, that might not detectable at compile time - but something like

f : U8 -> U8
f = \x ->
  ...
  g = -x * -x
  ...
  g

the compiler could provide today with no language changes

view this post on Zulip Richard Feldman (Mar 25 2023 at 18:47):

ahh sure, that's a good point!

view this post on Zulip Richard Feldman (Mar 25 2023 at 18:47):

so just like "if we know for sure it's gonna be a problem, we can tell you; otherwise, you might get a runtime error"


Last updated: Jul 06 2025 at 12:14 UTC