roc-lang / roc

A fast, friendly, functional language.
https://roc-lang.org
Universal Permissive License v1.0
3.86k stars 284 forks source link

Destructuring like `[a, b] = ["foo"]` should fail, but value for `b` is held over from previous walk element #6816

Open trespaul opened 2 weeks ago

trespaul commented 2 weeks ago

I was trying to destructure a list in a way that shouldn't have worked, but it did. In the case where it definitely should have failed, it carried over the value for the destructuring from the previous element in the list.

@bhansconnect pointed out that this shouldn't have worked, and asked me to open an issue.

MWE:

app [main] {
    pf: platform "./basic-cli/platform/main.roc",
}

import pf.Stdout

main =
    myList = [
        "Seoul;8.7",
        "Minneapolis;3.5",
        "El Paso;22.0",
        "Foo",
        "Helsinki;9.1",
    ]

    result = List.walk myList (Dict.empty {}) \state, elem ->
        cityWithTemp = Str.split elem ";"
        [city, temp] = cityWithTemp # <-- here

        Dict.update state city \value ->
            when value is _ -> Present { temp }

    Inspect.toStr result |> Stdout.line!

Output of the above (formatted):

{
    "Seoul":        {temp: "8.7"},
    "Minneapolis":  {temp: "3.5"},
    "El Paso":      {temp: "22.0"},
    "Foo":          {temp: "22.0"},
    "Helsinki":     {temp: "9.1"}}
}

When the walk got to "Foo", it should've failed to assign temp, but it kept the value from El Paso.

(My roc version is built from 5d09479 on Jun 13)

GabrielBoehme13 commented 1 week ago

It's likely I'm missing something Roc-specific here (implicit runtime decoding checks taking place behind the scenes?), but I'm surprised that this destructuring syntax would even compile:

        cityWithTemp = Str.split elem ";"
        [city, temp] = cityWithTemp # <-- here

I got a silent failure in the web REPL today (with a JS console TypeError) when I tried entering the List.walk section of the code above, even after I simplified it to this:

    result = List.walk myList (Dict.empty {}) \state, elem ->
        cityWithTemp = Str.split elem ";"
        [city, temp] = cityWithTemp
        Dict.insert state city { temp }

I then rewrote the List.walk section like this instead, to explicitly handle situations where you don't get two elements from Str.split (and to test my current understanding of how Roc works):

    result = List.walk myList (Dict.empty {}) \state, elem ->
        when Str.split elem ";" is
            [city, temp] ->
                # if the same city is listed more than once, its last temp value wins
                Dict.insert state city { temp }
            _ ->
                # ignore lines which aren't in the expected format
                state

This worked in the web REPL, with Inspect.toStr result showing the expected output (formatted):

{
    "Seoul":    {temp: "8.7"},
    "Minneapolis":  {temp: "3.5"},
    "El Paso":  {temp: "22.0"},
    "Helsinki": {temp: "9.1"}
}