Is there a concise way to extract a payload from a tag result?
For example, suppose I have the following code:
## Convert a list of string represented values (ordered by level) into a binary tree
## values may be a string represented integer, or "null"
listToTree : List Str -> NodeOrNull
listToTree = \lst -> listToTreeRecur lst 0
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
if (List.len lst) <= index then Null
else
strVal =
when List.get lst index is
Ok str -> str
Err _ -> ""
if strVal == "null" || strVal == "" then Null
else
val =
when Str.toI64 (strVal) is
Ok num -> num
Err InvalidNumStr -> 0
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
I would like to do something like val = Str.toI64 (List.get lst index)
but of course both Str.toI64
and List.get
return their values as payloads in a tag union.
Is there a more concise way to express this so I can chain functions together, instead of having to create temporary variables to store values?
Generally checking length explicitly leads to extra branching/more explicit code :if (List.len lst) <= index then
Instead, just use the result of List.get
for checking that
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
when List.get lst index is
Ok str ->
if str == "null" || str == "" then Null
else
val =
when Str.toI64 (str) is
Ok num -> num
Err InvalidNumStr -> 0
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
Err _ ->
Null
Next, you can use the Result helpers here.
For example:
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
when List.get lst index is
Ok str ->
if str == "null" || str == "" then Null
else
val = Str.toI64 str |> Result.withDefault 0
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
Err _ ->
Null
You also can use guards in when blocks:
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
when List.get lst index is
Ok str if str != "null" && str != "" ->
val = Str.toI64 str |> Result.withDefault 0
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
_ ->
Null
A general note:
If you have multiple results functions in a row and don't want to check each and every one, that is what Result.try and Result.map are for. It would enable chaining multiple result functions together. You could instead write something like:
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
nodeRes =
str <- List.get lst index |> Result.try
# Note, Str.toI64 will reject "null" and "", so I am just using it here, but that may not be ok in general
val <- Str.toI64 str |> Result.map
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
Result.withDefault nodeRes Null
Wow, this is great. I'm still in progress learning language features and syntax, so your code examples here are a huge help in fleshing out some of these ideas. Really appreciate your answers here.
Everything here makes sense up to the last example using Result.try -- I've been trying to understand the back passing syntax, but the more I puzzle over the tutorial section on back passing, the more confused I get.
As I understand it, str <-
defines an anonymous function with an argument named str
, much like \str ->
would. But where is the body of the function written?
Also the |>
operator will pass the return of one function on to the following function. It makes sense that Since Result.try expects a Result and a function to transform the Result as arguments, and that since List.get
returns a result, this result could be passed on as the first argument to Result.try
, but how does the <-
operator interact with |>
? Where is the body of the function being passed to Result.try? I assume that this function is also the same as the body of the function defined by str <-
...
Why not:
# return val from `List.get` piped to `Result.try `as first argument, anon func `str <- some code` passed as second arg:
List.get lst index |> Result.try str <- some.func body code which operates on str
Not sure why this back passing is seeming so incomprehensible to me... seems like I should be able to figure out the syntax just from your code snippet here.
backpassing fundamentally removes indentation
The body of the function is all of the lines of the code after that line on the same indentation level
Without backpassing it would be:
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
nodeRes =
List.get lst index |> Result.try \str ->
# Note, Str.toI64 will reject "null" and "", so I am just using it here, but that may not be ok in general
Str.toI64 str |> Result.map \val ->
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
Result.withDefault nodeRes Null
Okay, this makes sense. Seems counterintuitive based on my understanding of the pipe |>
operator passing everything preceding the pipe as the first argument to the function after the pipe. Thanks again though. That does straighten out the confusion. :saluting_face:
It is doing that. If you desugar the pipe as well:
listToTreeRecur : List Str, U32 -> NodeOrNull
listToTreeRecur = \lst, index ->
nodeRes =
Result.try (List.get lst index) \str ->
# Note, Str.toI64 will reject "null" and "", so I am just using it here, but that may not be ok in general
Result.map (Str.toI64 str) \val ->
rhs = listToTreeRecur lst (index * 2 + 1)
lhs = listToTreeRecur lst (index * 2 + 2)
Child {val, rhs, lhs}
Result.withDefault nodeRes Null
Yeah, I see that... just throws me off that the str <-
precedes the |>
even though the function it is defining will be the second argument to Result.try
or the val <-
function will be the second argument to Result.map
.
Seems like the <-
operator modifies the expected behavior of |>
. This is something one can learn and remember, just a little annoying that it appears to break the rules.
Yeah, desugaring has an order, so makes sense that it can be a bit confusing.
Yeah, guess you're right, that's what it boils down to.
Ian McLerran has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC