As a learning exercise, I'm trying to write a function that attempts to read a file that may not exist, and then:
FileReadErr
) to a single error (ie OutputError) with a message str, so it can be handled later.Real use case:
My haphazard attempts to get this to work have failed. Here's the latest failed attempt:
main =
outputFileMapErr
|> Task.onErr handleErr
outputFileMapErr =
# A file that does not exist
path = Path.fromStr "someFile"
# This could fail if the file doesn't exist or is unreadable
result <- File.readUtf8 path
|> Task.attempt
|> Task.mapErr \err -> Task.err MyCustomErr "Some error message."
handleErr =
msg = when err is
MyCustomErr msg -> msg
Stdout.line "We stumbled upon this: $(msg)"
What could I do with the results of File.readUtf8
to get the desired behavior?
Hi! I've played around with that code a bit and made a couple of tweaks to get to this modified version:
main =
outputFileMapErr
|> Task.onErr handleErr
outputFileMapErr =
# A file that does not exist
path = Path.fromStr "someFile"
# This could fail if the file doesn't exist or is unreadable
result <-
File.readUtf8 path
|> Task.mapErr \err -> MyCustomErr "Some error message."
|> Task.await
Stdout.line result
handleErr = \err ->
when err is
MyCustomErr msg ->
Stdout.line "We stumbled upon this: $(msg)"
Two small fixes:
Task.mapErr
takes a function that maps from the original task's error to some new error. You don't have to wrap that new error in Task.err
, if you do then you create a Task with an error type that is itself a task, a task nested within another task if you will.Task.attempt
gives you Result
type, which you could pattern match on to handle the success and failure cases. Because you're already handling the error using Task.mapErr
you can instead use Task.await
, which provides you just the success value. Either way, Task.attempt
or Task.await
should go on the end of the pipeline.Let me know if anything is unclear, or if I misunderstood what you were trying to do!
Thank you Jasper! That was very helpful.
For anyone trying to understand backpassing and the pipe operator, this is what the function above looks like without them:
outputFileMapErr =
# A file that does not exist
path = Path.fromStr "someFile"
# Task.mapErr takes two arguments:
# - A task
# - A function that takes an error and outputs another error
Task.mapErr
# First arg should be a Task, which is what calling Task.await produces
# NOTE that we must wrap the call to Task.await in parentheses
# so we pass the resulting Task to mapErr, and noth the individual
# parts (Task.await itself, the result from calling readUtf8, the anonymous function)
(Task.await
# Task await _also_ takes a task as first arg, which is what
# File.readutf8 produces. Again, we wrap in parentheses to
# pass the output and not the function and _path_ individually
(File.readUtf8 path)
# The second arg to await is a function that _also_ returns
# a task.
\result -> Stdout.line "What we got is: $(result)")
# The second arg to mapErr is a function that takes an error and
# returns a different error.
# Note that the MyCustomErr tag is nothing special: we could have
# used any tag name, as long as it follows the tag naming conventions.
\_ -> MyCustomErr "Some error msg."
Relevant docs:
Noel R has marked this topic as resolved.
@Noel R , your desugaring is not quite right. That said, I think your reordering is also correct in this specific case.
This is an exact desugaring.
outputFileMapErr =
# A file that does not exist
path = Path.fromStr "someFile"
# This could fail if the file doesn't exist or is unreadable
Task.await (
Task.mapErr (File.readUtf8 path) \err -> MyCustomErr "Some error message."
\result -> Stdout.line result
The nesting of Task.await
and Task.mapErr
was flipped.
Noel R has marked this topic as unresolved.
Thank you Brendan for pointing that out. This sugar sprinkling / desugaring is a good learning exercise.
Last updated: Jul 26 2025 at 12:14 UTC