hi , I want to read file store it somewhere and than let say use it in another task but I don't know how to do that
one thing I want to loop this second task
This is an example with a mix of file io: https://github.com/roc-lang/roc/blob/main/examples/cli/file.roc
Reading from a file is specifically: contents <- File.readUtf8 path |> Task.await
more docs here: https://www.roc-lang.org/packages/basic-cli/File
great but this does not wok for me, first of all I can't get result out of one task and pass it to the other which is looping
I would need to see more context to understand. Can you share code or a simplified example?
task =
path = Path.fromStr "out.txt"
contents <- File.readUtf8 path |> Task.await
Stdout.line "I read the file back. Its contents: \"\(contents)\""
Task.attempt task \result ->
when result is
Ok {} -> Stdout.line "Successfully wrote a string to out.txt"
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 |> Task.await
Process.exit 1
I want to extract content file from previous and run something like that in the other task
to process it further
Task.loop {} \_ -> Task.map in_this_task_I_want_to_process_file_content Step
or I want to run this loop inside
Task.attempt task \result ->
basically I have no clue how I could encapsulate or queue Task.attempt with other tasks
this way works more or less
read =
path = Path.fromStr "log.txt"
hien <- File.readUtf8 path |> Task.await
_ <- Stdout.line "file read" |> Task.await
loopTask =
text <- Task.await Stdin.line
Stdout.line "You just entered: \(hien)"
Task.loop {} \_ -> Task.map loopTask Step
Task.attempt read \result ->
when result is
Ok {} -> Stdout.line "Successfully wrote a string to out.txt"
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 |> Task.await
Process.exit 1
I've made an issue to create/assemble some Task.loop
examples with explanation.
Do you have any other ideas for how we could have made this easier for you @Artur Swiderski?
I've also started a discussion about another potential usability improvement.
I don't get idea of task at all, I am using it but I don't know what I am doing. I can't say the same about C++ threads for example when I do understand what it means and how it relates to OS internals
I see, Tasks are used in Roc because they allow us to separate "dangerous" and "safe" code. This makes it easier to create reliable programs. When writing to a file in for example python, you will by default not handle any errors that can come up. But it's not at all obvious that you are doing anything dangerous that has a significant chance of crashing your program. Depending on the application, crashing can range from being annoying to risking lives. With Tasks, we make it obvious what can go wrong and give the programmer the opportunity to handle an error without crashing the program.
In the future, tasks will also allow us to record the running of your program so you don't have to manually mock things for tests.
Tasks also give us the opportunity to never need to run specific tests unnecessarily because we can be sure the result has not changed.
When checking the source code of a Roc program for security or privacy risks, tasks allow you to focus your attention on the dangerous parts of the code.
I am working on a new Roc platform named roc-script that takes a less strict approach with the error handling intended for less "sensitive/critical programs" like scripts. That should make programming easier compared to the basic-cli platform.
Feel free to ask me more questions :)
I've also created three issues to explain the benefits of Task
in more places.
I'm not sure "dangerous" is a good term especially given it sounds kinda like "unsafe", which has a very specific meaning.
Fundamentally, a task must wrap any effect that your program can do. Generally speaking, an effect means any sort of input/output to the program (network requests, file io, stdin/out/err, requesting a random seed from the OS, getting the time, etc)
If something is wrapped in a task it doesn't necessarily return the same result. For example, if you read the same file twice it may give you totally different results.
Task is the only special part of roc where that can happen. All of the rest of roc can be fully reasoned about. If you have a roc function without any tasks in the type signature, no matter how many times you call it, if you pass in the same args, you will get the same result. This is huh for understanding and debugging a program.
Side note: Tasks being designed to expose many classes of errors is an option but not strictly required. One could design tasks with no errors that instead always crash (essentially what roc-scripts will do). This is another reason I am not sure "dangerous" is the best wording.
@Artur Swiderski, your example that works more or less looks correct. You could also use the state variable of Task.loop
to pass the file into the loop if wanted. Or just define the entire loopTask as part of the lambda passed to Task.loop
. Any specific questions you have around it?
thx I will live with what I have for now
thx for clarification btw. so task is an abstract concept not real thing like thread. I explains why it is so difficult for me to grasp it
and one more thing how task relates to main , and how roc main relates to main in C/C++
In essentially all platforms, main is a task. So it is an operation or a chain of operations to run.
At least for the roc-cli platform main, you can think of main as essentially the same thing as c/c++ main.
I think the c/c++ main can be seen as a "real main", the roc main is kind of fake. The roc platform (like basic-cli) contains the real main and this one calls the roc main function.
The platform is "fixed" and expects a roc main function with a specific signature Task {} I32
. The I32 is the exit code of your program, like you would do return 1
in C++. The {}
is empty because you don't want to pass a value to the platform if all operations succeeded. The roc main signature is not just I32
like in C++ because we want to clearly indicate "effectful" code with a Task
. Only the platform can execute the Task.
about signatures what does it even mean: main : Task {} [] or Task {} I32 , clearly something different than function, at the same time since C everything what I can execute is function there is no confusion. How to pass parameters to it(like passing parameters to application)? What is the way to chain tasks? can I just run them in parallel if needed. How task produces its return value it is the value of last executed statement ? What does it return in that case "Task.loop {} \_ -> Task.map loopTask Step"
this example from roc-lang/examples has some extra explanation on Tasks.
Update: TaskUsage I just pushed a temporary build of the examples so you can see it rendered.
It does get rendered nicely if you build the examples site, but I dont think we have a good link I can share RN, hopefully on the next site update all the examples will be available.
Basically Task {} I32
means that it will succeed and return an empty record {}
or fail and return an 32-bit integer.
does it even mean:
main : Task {} []
Here we say that the value main has the type Task {} []
. Given that it takes no arguments it is indeed not really a function but a value. Similar to what Luke said, in the case of Task a b
, a
is the success value and b
is the error value.
How to pass parameters to it(like passing parameters to application)?
We currently have an issue with passing command line arguments to the application but here is the code to do that.
What is the way to chain tasks?
Luke's example explains that.
can I just run them in parallel if needed.
We plan to support that in the future.
How task produces its return value it is the value of last executed statement?
The return value will be produced by the last line of code in the function.
What does it return in that case "Task.loop {} \_ -> Task.map loopTask Step"
The loop
function returns a Task with either a success value or an error value.
Please link this doc-s https://www.roc-lang.org/packages/basic-cli/0.5.0/ better, I was even not aware of its existence although I am using the language from time to time few months now
I'm happy to, they are linked on the platform's github repo, do you have any suggestions where else they could be linked?
I think that, when I go https://www.roc-lang.org/tutorial and press doc button this doc should be there available too. You have to be aware that, at least initially for most users roc has potential to be at most like second, third language. It means those people will dig just enough to get things done. So tutorial and doc pointed there, will be everything they are using. If some essential information is missing it may cause frustration and discouragement.
It is trade off between my curiosity of new things and the time I have to spend. That's the reason why information has to be linked well, many times in the past when I couldn't complete something like within 3-4 hours, because of lack of knowledge I just drop it altogether, because this new hot thing was not essential to me. and roc is one of those things I think is nice but is novelty and I can do without it.
on other question when I want to loop function let say 100k times I can do it by recurrence, but I see that playing with Task.loop could also do the trick, which is more efficient from performance point of view?
Can you share what kind of function you want to loop 100k times? That will allow me to give the best recommendation
actual function is complex but in essence is something as follows
fun = \ list, cnt ->
fun (List.append list 1) (cnt -1)
in real application I am accumulating more complex result in a list(few lists in struct), there are some calculation involved, but it boils down to this at the end
In the real function are you using another function that returns a Task? A function that does not use any tasks will be faster.
no side effects in that function so no tasks
Ok, then you will not need Task.loop. You probably want to use something like List.map, List.walk, List.walkWithIndex...
Feel free to share the whole program, lots of people here like performance optimization.
For maximum speed make sure to use roc build --optimize yourprogram.roc
:)
If you did have a Task to start with and then want to use your function without Task, you probably want to also use Task.map.
ok thx roc build --optimize what does it do ?
It makes LLVM do a lot more optimization, we don't enable it by default because it also takes a lot longer to build.
Roc code is converted to an LLVM representation and LLVM converts that to machine code.
The C++ compiler also produces this LLVM representation.
Last updated: Jul 06 2025 at 12:14 UTC