I built a function that converts hh:mm:ss:ms
timestamp to milliseconds. In the process I discovered that Error handling got quite verbose (yet informative). Something tells me that this is too much... What would be a recommended way to make this logic a little shorter?
timeStampArrToMillsec = \timeStampArr ->
when timeStampArr is
[hours, minutes, secondsMilliseconds] ->
hoursInt = when (Str.toI32 hours) is
Ok val -> val
Err _ -> crash "Non numerical value passed to hours"
minutesInt = when (Str.toI32 minutes) is
Ok val -> val
Err _ -> crash "Non numerical value passed to minutes"
seconds = when (List.first (Str.split secondsMilliseconds ".")) is
Ok val -> val
Err _ -> crash "Seconds value does not exist in an array "
milliseconds = when (List.last (Str.split secondsMilliseconds ".")) is
Ok val -> val
Err _ -> crash "Milliseconds value does not exist in an array "
secondsInt = when (Str.toI32 seconds) is
Ok val -> val
Err _ -> crash "Non numerical value passed to seconds"
millisecondsInt = when (Str.toI32 milliseconds) is
Ok val -> val
Err _ -> crash "Non numerical value passed to milliseconds"
(hoursInt * 3600000) + (minutesInt * 60000) + (secondsInt * 1000) + millisecondsInt
_ -> crash "Invalid agruments passed as timestamp"
expect
((timeStampArrToMillsec ["00","00","00.100"]) == 100)
&& ((timeStampArrToMillsec ["00","00","01.100"]) == 1100)
&& ((timeStampArrToMillsec ["00","01","01.600"]) == 61600)
This is a nice improvement:
orCrash : Result a err, Str -> a
orCrash = \res, errMsg ->
when res is
Ok val -> val
Err _ -> crash errMsg
hoursInt = orCrash (Str.toI32 hours) "Non numerical value passed to hours"
I liked using Result.try
with backpassing when parsing dates here! https://github.com/rtfeldman/roc-iso8601/blob/dfbadf1074d3c5bfe9098de01c90745b566c51e0/Iso8601.roc#L310
Is this how you would use Result.try
if you wanted to keep the custom error messages?
timeStampArrToMillsec = \timeStampArr ->
when timeStampArr is
[hours, minutes, secondsMilliseconds] ->
hoursInt <-
Str.toI32 hours
|> Result.mapErr \_ -> "Non numerical value passed to hours"
|> Result.try
minutesInt <-
Str.toI32 minutes
|> Result.mapErr \_ -> "Non numerical value passed to minutes"
|> Result.try
seconds <-
List.first (Str.split secondsMilliseconds ".")
|> Result.mapErr \_ -> "Seconds value does not exist in an array"
|> Result.try
milliseconds <-
List.last (Str.split secondsMilliseconds ".")
|> Result.mapErr \_ -> "Milliseconds value does not exist in an array"
|> Result.try
secondsInt <-
Str.toI32 seconds
|> Result.mapErr \_ -> "Non numerical value passed to seconds"
|> Result.try
millisecondsInt <- Str.toI32 milliseconds
|> Result.mapErr \_ -> "Non numerical value passed to milliseconds"
|> Result.map
(hoursInt * 3600000) + (minutesInt * 60000) + (secondsInt * 1000) + millisecondsInt
_ -> Err "Invalid agruments passed as timestamp"
expect
((timeStampArrToMillsec ["00","00","00.100"]) == Ok 100)
&& ((timeStampArrToMillsec ["00","00","01.100"]) == Ok 1100)
&& ((timeStampArrToMillsec ["00","01","01.600"]) == Ok 61600)
Yeah, Result.try
with backpassing and 1 final when is the way to go. Can add some error type mapping or wrapping if you want as well.
I would use a tag and not a string for the error.
I also might just use wrapping here and add details later
but yeah, not as great
I would do this if i really cared about context
hoursInt <- Str.toI32 hours |> Result.mapErr HoursErr |> Result.try
minutesInt <- Str.toI32 minutes |> Result.mapErr MinutesErr |> Result.try
...
Then a when to match on all error types
In Rust, I would want to use an error enum but give it a custom Display
implementation that printed the human-readable message. As an API author in roc, if you want your error to print nicely with e.g. dbg
, it seems like you need to return a Str
as your error type
when res is
Ok x -> ...
Err e -> toErrorMessage e
toErrorMessage = \err ->
HoursErr subErr -> ...
MinutesErr subErr -> ...
Tags will print there name, which is a medium ok message, but yeah, you probably want a toMessage
function
That or make your error type opaque and add a custom inspect impl, but I would definitely advise just using a when ... is
in a function to generate the error message.
Easy enough for a user to do either of:
dbg err
# or
dbg Lib.toMessage err
thank you very much for the input, Anton's orCrash
was the most straightforward at my level and it made code a bit more concise indeed. Examples from Richard, Tim and Brendan went over my head, I don't understand how piping Result.try
works actually in this context (even though I consulted the docs). You've been very generous with your help though, I just need to run some experiments to internalize Result.try
. Thanks again
Result.try
works there thanks to backpassing. It's an important piece of the equation there!
Last updated: Jul 06 2025 at 12:14 UTC