Stream: beginners

Topic: ✔ Concise syntax to extract payload from a tag?


view this post on Zulip Ian McLerran (Jan 10 2024 at 16:44):

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?

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 16:46):

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

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 16:47):

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

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 16:49):

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

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 16:51):

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

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 16:58):

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

view this post on Zulip Ian McLerran (Jan 10 2024 at 20:11):

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.

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 20:19):

backpassing fundamentally removes indentation

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 20:19):

The body of the function is all of the lines of the code after that line on the same indentation level

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 20:20):

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

view this post on Zulip Ian McLerran (Jan 10 2024 at 20:42):

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:

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 20:45):

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

view this post on Zulip Ian McLerran (Jan 10 2024 at 20:48):

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.

view this post on Zulip Brendan Hansknecht (Jan 10 2024 at 20:55):

Yeah, desugaring has an order, so makes sense that it can be a bit confusing.

view this post on Zulip Ian McLerran (Jan 10 2024 at 20:57):

Yeah, guess you're right, that's what it boils down to.

view this post on Zulip Notification Bot (Jan 10 2024 at 20:57):

Ian McLerran has marked this topic as resolved.


Last updated: Jul 06 2025 at 12:14 UTC