What would be the equivalent of "finally" for executing a task? I want to execute a task, store its result, then execute another task (cleanup that should always happen), then return the result. Here's kind of what I'm trying:
app = {}
Tty.enableRawMode! {}
loopTask = Task.loop app gameLoop |> Task.mapErr (\_ -> Exit 0 "Error")
# TODO execute loopTask, cache its result, disable raw mode no matter what, then return result
Tty.disableRawMode! {}
result
Task.ok result
https://www.roc-lang.org/builtins/Task#ok
I pass loopTask
into Task.ok?
to clarify, by store the result of loopTask, I mean the Result (which can be Ok or Err)
You are mapping the error using Task.mapErr
so you won't get a result from Task.loop. If you want that you should use something like loopTask = Task.loop app gameLoop |> Task.result!
app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }
import cli.Stdout
main =
Stdout.line! "enableRawMode"
result = Task.loop! {} gameLoop
Stdout.line! "disableRawMode"
Stdout.line "Got: $(Inspect.toStr result)"
gameLoop = \{} -> Task.ok (Done Answer)
$ roc example.roc
enableRawMode
disableRawMode
Got: Answer
There isn't any Result being used here though. Task.loop
Jared Cone said:
to clarify, by store the result of loopTask, I mean the Result (which can be Ok or Err)
What is the type of your gameLoop
function?
In your code snippet, if Task.loop! {} gameLoop
fails, will Stdout.line! "disableRawMode"
still execute? I thought it would not
I think this might be what I wanted:
main : Task {} [Exit I32 Str]_
main =
app = {}
Tty.enableRawMode! {}
Task.loop app gameLoop
|> Task.attempt
(\result ->
Tty.disableRawMode! {}
Task.fromResult result)
|> Task.mapErr (\_ -> Exit 1 "Error")
gameLoop : AppState -> Task [Done {}, Step AppState] _
There's definitely some frustrating compiler bugs lurking around here. I keep seeing when I try different things
thread '<unnamed>' panicked at crates/compiler/mono/src/ir.rs:6151:56:
called `Option::unwrap()` on a `None` value
https://gist.github.com/lukewilliamboswell/fac30e97e14793c078369d6afa26aa63
Anyway, glad you have something working for now
ty
I would have expected this to work. But it's hitting that error.
app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }
import cli.Stdout
main =
Stdout.line! "enableRawMode"
gameState =
Task.loop {} gameLoop
|> Task.onErr! \_ ->
Stdout.line! "got an error... cleaning up then exiting"
Stdout.line! "disableRawMode"
Task.err (Exit 1 "SomethingBlewUp")
Stdout.line! "disableRawMode"
Stdout.line! "Got: $(Inspect.toStr gameState)"
# crashes with this
#thread '<unnamed>' panicked at crates/compiler/mono/src/ir.rs:6151:56:
#called `Option::unwrap()` on a `None` value
#note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
gameLoop = \{} -> Task.err SomethingBlewUp
# works with this
#$ roc example.roc
#enableRawMode
#disableRawMode
#Got: Answer
#gameLoop = \{} -> Task.ok (Done Answer)
Looks like a lambda set specialization issue. This is what crashes
let spec_symbol_index = iter_lambda_set.next().unwrap().0;
Different error when running debug build of the compiler...
$ nix develop
$ cargo run -- example.roc
thread '<unnamed>' panicked at crates/compiler/mono/src/layout.rs:2065:17:
unspecialized lambda sets left over during resolution: LambdaSet([] + (<2127>FlexAble(*, [`Inspect.Inspect`]):`Inspect.toInspector`:2), ^<2130>), UlsOfVar(VecMap { keys: [2127], values: [VecSet { elements: [2126, 2129] }] })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Ok, so the issue is cause by Inspect.toStr
here.. removing that and it works as I expected.
app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }
import cli.Stdout
main =
Stdout.line! "enableRawMode"
# ignoring the game state
_ =
Task.loop {} gameLoop
|> Task.onErr! \_ ->
Stdout.line! "got an error... cleaning up then exiting"
Stdout.line! "disableRawMode"
Task.err (Exit 1 "exiting because we got an error running the game loop")
Stdout.line! "disableRawMode"
Stdout.line! "SUCCESS"
gameLoop = \{} -> Task.err SomethingBlewUp
#gameLoop = \{} -> Task.ok (Done Answer)
$ cargo run -- example.roc
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/roc example.roc`
enableRawMode
got an error... cleaning up then exiting
disableRawMode
exiting because we got an error running the game loop
As a general note, I think Task.result is closer to what you were looking for. That can be used to merge the ok and error case enabling a "finally"
Stdout.line! "enableRawMode"
res = Task.loop ... |> Task.result!
Stdout.line! "disableRawMode"
Task.fromResult! res
We probably could add a Task.finally
to make this simpler
sure - plus, in the Purity Inference world, this entire category of problem goes away :smiley:
Does it? You still have the case of wanting a finally after a group of ?
on a result
I mean converting between Task
and Result
in general
Jared Cone has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC