The docs for Task.forever
say that it's a function that never returns, but it seems like it does return if the provided task returns an error. I think either the implementation should be changed to actually run forever (a.k.a. ignore errors), or the docs should change to say "It will run forever unless it returns an error, at which point it quits looping" or something to that effect. Here's the source from basic-cli
:
## Run a task that never ends. Note that this task does not return a value.
forever : Task a err -> Task * err
forever = \task ->
looper = \{} ->
task
|> InternalTask.toEffect
|> Effect.map
\res ->
when res is
Ok _ -> Step {}
Err e -> Done (Err e)
Effect.loop {} looper
|> InternalTask.fromEffect
I was first thinking that it would be best to keep the implementation the same, since people likely already use the function, but then I think the name is actually misleading (for the above reasons). If we keep a function with the same intended behavior, I think we should rename it to something more descriptive. Some suggestions:
Task.repeatUntilFail
Task.loopUntilFail
Task.goUntilErr
Anything better?
In the meantime, a Task.forever
that actually runs forever would be nice, but changing the signature without changing the name would mean a lot of code might break. It seems like we're still at the "breaking stuff is fine" stage, but I'm not sure if that would be a good idea.
All in all, I vote for two changes to the Task
interface:
1) Create a new Task.repeatUntilFail
function with the signature Task * err -> Task * err
that loops forever the same Task until it fails, at which point it returns.
2) Change the signature of Task.forever
to Task * * -> Task * *
and have it loop forever, ignoring errors.
Feel free to share your thoughts.
I would vote for Task.repeatUntilErr
or Task.repeatUntilError
. Cause it is Task.err
that it is repeating until
That said, I also think forever
is a fine name with the doc saying, repeats forever unless an error ocurs.
Don't have much of an opinion about a Task.forever
that truly repeats forever. Personally, I would expect to accidentally use it when still expecting errors to fail and then be really confused.
Cause my mental model of tasks separates the success case from the error case. So I kinda always expect things to fail on any sort of error.
I'm curious, what use case did you have in mind for the function that would truly loop forever, ignoring errors? Closest thing I can come up with is something like a server or game loop, but even that I'd want to be able to stop eventually to quit.
Yeah, I agree there isn't much of a use case.
You can always just make the error variants empty and use the "return on error" variant
So I guess I just propose we rename and re-document Task.forever
From this discussion, I think we should keep the current name and just update the docs to be clearer about how it works in the error case. It is true that if there are no errors it never ends, so the happy path behaviour is consistent.
@Brendan Hansknecht then I guess the question is whether a slight improvement in function name clarity (I think Task.repeatUntilErr
is clearer than Task.forever
) is worth breaking existing code. I think it is worth it to get it out of the way, since this function isn't probably used too much.
@Luke Boswell do you think it is more clear but not worth the breakage, or it's not much clearer?
I'm not convinced it's any clearer.
I'm always happy to break things, but in this case I think the shorter name is better.
Okay, I'll keep the name the same for now
Last updated: Jul 06 2025 at 12:14 UTC