Hi! I thought I learn a functional language close to roc to get an idea of how to use the language to write stuff without using roc itself, because there is little documentation about how to use the language so far (and because there is no language server yet :nerd: ) What languages can I look at that are probably easy to translate to roc? I thought about Elm, but maybe it's too limiting? PureScript seems viable, although it has a few different syntax decisions, it seems to have similar effects systems.
My background: I tried to learn Haskell a few times, but I never reached the point where I could write applications that would actually do something useful. I once reached the point where I understood that there's no point in understanding Monads, because each Monad does a different thing. Then I saw that there are different solutions to combining different Monads and was lost in the way from understanding the basics of the language to writing a program that actually does something useful, which is really unfortunate. So PureScript 1. seems really close to Haskell and to have all the features that roc also plans to have 2. the book seems to have a plan for going from the basic to writing applications with Effects and doesn't stop somewhere in the middle and leaves you hanging. So before I invest a lot of time in a language I only learn to later write non-web-apps in roc, would you recommend a different learning path (you, who maybe went through similar learning paths)?
I started using Roc by going through the Advent of Code challenges. I found that to be fun, and the community here was very helpful when I ran into issues. I would recommend that for anyone interested in learning more about Roc. It was really helpfulto see other peoples approaches to the same problems. :big_smile:
The good thing about AoC I find is that they cover a broad range of problems, so you can learn lots of different things. The problems are careful to keep the scope focussed, but still interesting. The difficulty scales pretty nicely too, so there is something there for everyone. I think Roc is a great language and these problems really highlight just how nice it is to use. I recall modelling using Tags, no type annotations, and testing using expect
being low friction which made it easy to be productive and solve the problem, but still having the comfort of the compiler and type checking to catch issues.
To directly answer the question, Roc is described as a direct descendant of Elm, so that's definitely the closest. PureScript and Haskell are usually considered harder to learn, with more focus concepts and less on just building stuff.
Regarding Elm possibly being too limiting: The main difference is what kind of IO or effects you can do, because it's very front end focused. Pure logic is very similar between the two.
But if your goal is to learn Roc, it might be less confusing to just focus on Roc alone, by looking at AoC solutions, as Luke suggested!
Do you have an example repository with the necessary setup? Like which platform to use, how to read files?
Many people went through AOC last year and there is a lot of links and discussion in #Advent of Code. The examples may be slightly out of date due to Roc changing, but I think it should be relatively minor. I think essentially everyone uses the basic-cli platform. This is my day1 part1, for example. I think to make it work, it would just require updating the platform path to: https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br
@itmuckel I updated my AoC 2022 examples and checked they all compile with the latest version of Roc. These might help you if are looking for inspiration - for at least one way to solve the problems. :big_smile:
I was trying to learn how to do parsing in Roc around that time, and I set myself the challenge of trying to parse the input files. So some of the early examples are probably overkill, some simple string splitting around \n
characters would have been quicker.
Great, thank you all!
Let me just reuse this thread:
task =
contents <- File.readUtf8 path |> await
chunks <- (Str.split contents "\n")
Stdout.line contents
I get the error
── TOO MANY ARGS ──────────────────────────────────────────────────── day1.roc ─
The split function expects 2 arguments, but it got 3 instead:
20│ chunks <- (Str.split contents "\n")
^^^^^^^^^
Are there any missing commas? Or missing parentheses?
But how? I even put it in parentheses and it still thinks it gets three arguments. Where is the third argument? :eyes:
Oooh, I get it now. backpassing creates a lambda that gets passed implicitly to Str.split, right?
Yeah, backpassing is creating a lambda here.
Kinda surprised it ignores parens and doesn't create a floating lambda, but same sort of problem either way.
Should just need to change from <-
to =
This one is really weird:
task =
contents <- File.readUtf8 path |> await
chunks = (Str.split contents "\n")
_ <- Stdout.line (List.first chunks |> Result.withDefault "err") |> await
Stdout.line (Str.concat "list: " (List.first chunks |> Result.withDefault "err"))
The first Stdout.line doesn't output anything (not even my "Success" message that should appear after the program ran).
When I comment out the first Stdout.line, my output is
list: 1000
success
like I would expect. What's going on with Result.withDefault? If I'm using it wrong, shouldn't there be an error?
Is the first line empty or just spaces?
That's my only immediate guess.
No, it contains "1000", otherweise the second Stdout.line wouldn't output "list: 1000", right?
Maybe it runs into an error, but reports nothing?
Task.attempt task \result ->
when result is
Ok {} -> Stdout.line "success"
Err err ->
msg =
when err is
FileWriteErr _ PermissionDenied -> "PermissionDenied"
FileWriteErr _ Unsupported -> "Unsupported"
FileWriteErr _ (Unrecognized _ other) -> other
FileReadErr _ _ -> "Error reading file"
_ -> "Uh oh, there was an error!"
{} <- Stderr.line msg |> await
Process.exit 1
can you share the full program and the full output, starting from the $ roc run ...
(or whatever you used to run it) command?
that would help I think!
Oh, interesting, it segfaults :-D
list: 1000
Segmentation fault
Yes, I only mashed together a cli-example and read a file, here is the full program:
app "day1"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [
pf.Stdout,
pf.Stderr,
pf.Process,
pf.Task.{ Task, await },
pf.Arg,
pf.File,
pf.Path,
]
provides [main] to pf
main : Task {} []
main =
path = Path.fromStr "input-day-1.txt"
task =
contents <- File.readUtf8 path |> await
chunks = (Str.split contents "\n")
_ <- Stdout.line (Str.concat "list: " (List.first chunks |> Result.withDefault "err")) |> await
Stdout.line (List.first chunks |> Result.withDefault "err")
Task.attempt task \result ->
when result is
Ok {} -> Stdout.line "success"
Err err ->
msg =
when err is
FileWriteErr _ PermissionDenied -> "PermissionDenied"
FileWriteErr _ Unsupported -> "Unsupported"
FileWriteErr _ (Unrecognized _ other) -> other
FileReadErr _ _ -> "Error reading file"
_ -> "Uh oh, there was an error!"
{} <- Stderr.line msg |> await
Process.exit 1
input-day-1.txt is:
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
I originally started it with roc dev day1.roc
and it doesn't show me that it segfaults, so I was asking here what was going on. roc build day1.roc
creates an executable that tells me that it segfaults
environment: WSL2 with roc nightly pre-release, built from commit 41d7ade on Sa 25 Mär 2023 09:07:07 UTC
Running on my machine prints both, but then kinda explodes at the end of the application run. no idea why:
https://gist.github.com/bhansconnect/bac5d18258987e6304a27671a12068e5
Maybe we recently had some sort of change or regression in basic-cli? I am quite confused by the output.
seems likely to be a basic-cli
problem
like some sort of memory corruption issue
could also be a builtin
valgrind
might have some insight?
==38947== Invalid read of size 1
==38947== at 0x484D40A: memrchr (in /nix/store/xkz8yydj9kl6l9r9bl2vn4q8lmfh02ns-valgrind-3.20.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==38947== by 0x96433C: memrchr_specific (memchr.rs:23)
==38947== by 0x96433C: memrchr (memchr.rs:39)
==38947== by 0x96433C: memrchr (memchr.rs:50)
==38947== by 0x96433C: write_all<std::io::stdio::StdoutRaw> (linewritershim.rs:248)
==38947== by 0x96433C: write_all<std::io::stdio::StdoutRaw> (linewriter.rs:206)
==38947== by 0x96433C: <std::io::stdio::StdoutLock as std::io::Write>::write_all (stdio.rs:731)
==38947== by 0x965BD0: <std::io::Write::write_fmt::Adapter<T> as core::fmt::Write>::write_str (mod.rs:1661)
==38947== by 0x593280: <&T as core::fmt::Display>::fmt (mod.rs:2361)
==38947== by 0x98D20B: core::fmt::write (mod.rs:1198)
==38947== by 0x964203: write_fmt<std::io::stdio::StdoutLock> (mod.rs:1672)
==38947== by 0x964203: <&std::io::stdio::Stdout as std::io::Write>::write_fmt (stdio.rs:711)
==38947== by 0x964A03: write_fmt (stdio.rs:685)
==38947== by 0x964A03: print_to<std::io::stdio::Stdout> (stdio.rs:1014)
==38947== by 0x964A03: std::io::stdio::_print (stdio.rs:1027)
==38947== by 0x25BBCE: roc_fx_stdoutLine (lib.rs:311)
==38947== by 0x23D4C5: roc_fx_stdoutLine_fastcc_wrapper (in /home/bren077s/Projects/roc-misc/roc/examples/day1)
==38947== by 0x236E49: Effect_effect_closure_stdoutLine_b7aa9f7d377b2692ada596045493ead6d491b934dc9015fcbdd1a8e01477d (in /home/bren077s/Projects/roc-misc/roc/examples/day1)
==38947== by 0x227C8D: Effect_effect_map_inner_1fee66ad667b912c4d10ada5f77fb9e8b2dfe9a4124f957b34ae7bc684ecaf1 (in /home/bren077s/Projects/roc-misc/roc/examples/day1)
==38947== by 0x22A8D3: Effect_effect_after_inner_8b8e749a7d5dc4035aed2d09b8b4ad59fac5ad694339521a2df23bf1ac35c3 (in /home/bren077s/Projects/roc-misc/roc/examples/day1)
==38947== Address 0x8000000004b7523b is not stack'd, malloc'd or (recently) free'd
I am gonna make a guess that somehow it is related to slice, but no clear reason why at the moment.
Oh, yeah, I think I see the issue. basic-cli is using a lock file. This means that it is not updating as roc-std
is updating. So it is using a version of roc-std
from before slices were added. Due to this, it is doing the wrong thing when passed in a slices.
A workaround until we update basic-cli
is to change the chunk line to:
chunks = List.map (Str.split contents "\n") Str.releaseExcessCapacity
@Anton do you think it is worth removing the lock file from basic-cli so that we always pull in the latest roc-std when publishing it? Otherwise, maybe we should add a cargo update
to the publishing process?
Brendan Hansknecht schrieb:
A workaround until we update
basic-cli
is to change the chunk line to:chunks = List.map (Str.split contents "\n") Str.releaseExcessCapacity
Perfect, that did it:
list: 1000
1000
success
Is there some documentation on where roc uses slices and what the implications are or shouldn't I care about them?
Also is there an issue for this problem (lock-file) that I can follow on github?
Just filed https://github.com/roc-lang/basic-cli/issues/25 to track
Fundamentally, a roc user should never have to think about slices, only a platform author might need to think of them. They are a performance improvement we added to reduce copying and extra allocations.
Some details here #contributing > PSA: Seamless Slice
yeah this is just a timing issue; they landed very recently in the compiler and represent only the second time the layout of that data structure has changed in Roc's history
so this shouldn't be something that comes up often!
I'll take a look at basic-cli#25 today
basic-cli 0.3.1 is building...
basic-cli 0.3.1 pre-release
Can you give it a try @itmuckel?
yes, that works :-)
@itmuckel I've made 0.3.1 the latest official release. The tar.gz is also no longer available, instead we have the much smaller tar.br
: https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br
Last updated: Jul 06 2025 at 12:14 UTC