gren-lang / compiler

Compiler for the Gren programming language
https://gren-lang.org
Other
379 stars 23 forks source link

exhaustive record pattern #247

Open lue-bird opened 4 months ago

lue-bird commented 4 months ago

Proposing syntax to make sure you've looked at all the fields of a record value. Adding or removing a field should in that case warn you that you need to explicitly ignore/use it.

Some common use cases

pattern syntax

This is consistent with {} being a match on a record with exactly 0 fields, instead of a record with whatever fields.

example code

update msg model =
    case msg of
        UserSelectedTrackColor { trackIndex = trackIndexToRecolor hue = selectedNewTrackHue } ->
            { model
               | tracks =
                    model.tracks
                        |> Array.update trackIndexToRecolor
                            (\track -> { track | hue = selectedNewTrackHue })
            }

type Msg =
    | UserSelectedTrackColor { trackIndex : Int, hue : Float }

if we then e.g. add

type Msg =
    | UserSelectedTrackColor { trackIndex : Int, hue : Float, lightness : Float }

the compiler will give an error about lightness not being handled in the update case branch.

Had this been an inexhaustive record match as in current gren, the user choice of lightness would simply be ignored, which is a pretty subtle bug!

similar mindset in existing features

conflicts

The proposed exhaustive record pattern does not allow matching on a value of an extensible record type.

This prevents a coding style where functions like view take info as an extensible record (also slightly related: https://github.com/gren-lang/compiler/issues/240).

alternatives considered

current workaround

Explicitly add a let declaration for the record with an explicit record fields type annotation which only has the fields you've considered.

discussion

This issue is mostly a summary of my points brought up in this discord thread

peteygao commented 4 weeks ago

This is a brilliant addition in my opinion and catches a lot of oversight-induced logic errors. There's been too many times to count where I added a new field to an Elm Record expecting behavioural change, only for it to not show up and scratching my head. Then waste time debugging going over conditional logic (most likely source of bugs) but finding nothing. Then after some more handwringing, I discover that I forgot to include a field in a record update somewhere.

Yeah, there's some trauma there 😹