Let's say that I have a record Foo
type alias Foo =
{ bar : Bar
, baz : Baz
}
and I pattern match on it like this:
encodeFoo : Foo -> Value
encodeFoo foo =
case foo of {bar, baz} -> ...
If I later add another member to Foo like this:
type alias Foo =
{ bar : Bar
, baz : Baz
, qux : Qux
}
I'd like to get a warning or error in encodeFoo where I'm now not matching all fields. In Haskell this is possible to achieve with
case foo of
Foo { bar = bar', baz = baz' }
Elm version: 0.19.
The pattern is actually exhaustive, it cannot fail to match... perhaps you meant a warning saying that you could get more fields out of that object? I don't see the value, and I think that it's common not wanting all the fields
Fair point, I've edited the description. Note that you would get an error in the same situation if Foo was a custom type or a tuple. The value here is the same as in those cases: fearless refactoring; if I change the definition of a record, the compiler will show me all places in the codebase that I should inspect and possibly update because of changed assumptions.
It'd be fine with me if this feature was optional, for example if there was some special syntax for saying "this pattern must match all fields" or a compiler switch.
@23Skidoo
In Haskell this is possible to achieve with
case foo of Foo { bar = bar', baz = baz' }Elm version: 0.19.
Doesn't your Haskell example behave the same way it would in Elm? Using record syntax, omitting parts of the record is acceptable. Since record syntax is just sugar for regular data types in Haskell, though, you can use the normal type matching syntax instead to make sure you're accounting for every field in Haskell, like this:
data Foo = Foo
{ bar :: String
, baz :: Int
}
case foo of
Foo bar baz ->
Here's some code to demonstrate this.
This isn't possible in Elm, though, as far as I'm aware, and I think it would be a valuable feature, particularly in cases such as JSON encoders where we probably don't want to forget any part of the record. In any case, any part of the record we don't intend to encode should be explicitly ignored rather than implicitly forgotten.
That said, most of the time it is preferable to be able to ignore the parts of a record that aren't needed, so the current behavior definitely shouldn't change. This would have to be new syntax.
Here's a workaround in Elm:
type alias Foo =
{ bar : String
, baz : Int
}
encodeFoo : Foo -> Value
encodeFoo { bar } =
string bar
safeEncodeFoo : Foo -> Value
safeEncodeFoo { bar } =
let
-- If there's an error here, did Foo's definition change?
-- Make sure you're encoding Foo correctly!
check : Foo
check =
Foo bar
in
string bar
Notice that encodeFoo works even though it doesn't account for every part of Foo, but safeEncodeFoo produces the following error:
-- TYPE MISMATCH --------------------------------------------------- Testing.elm
Something is off with the body of the `check` definition:
19| Foo bar
^^^^^^^
This `Foo` call produces:
Int -> Foo
But the type annotation on `check` says it should be:
Foo
which would prompt the following correction (in this case, intentionally ignoring baz):
safeEncodeFoo : Foo -> Value
safeEncodeFoo { bar, baz } =
let
-- If there's an error here, did Foo's definition change?
-- Make sure you're encoding Foo correctly!
check : Foo
check =
Foo bar baz
in
string bar
Here is the same feature request for GHC: https://gitlab.haskell.org/ghc/ghc/-/issues/15855.
Although it is titled "Warn about incomplete NamedFieldPuns patterns", I believe a more correct title would be simply: "Warn about any record pattern that does not provide a pattern for every field of the record" as suggested at https://gitlab.haskell.org/ghc/ghc/-/issues/15855#note_163191.
Do you have a specific use case?
Actually, I had the same reasoning for wanting such a warning as described there for GHC.
One has a record type declared, and a function that uses such a record (with record matching syntax). Initially, the programmer decided which fields he wants to inspect/use. When the program is modified and a new field is added to the record declaration, we might want to see a warning/error to pay attention to this function -- not to forget to update its definition ("either by handling the field or explicitly discarding it").
Three reasonable classes of use-cases to be considered are also described there in a comment:
Depending on programmer's intent, different kinds of warnings would be useful for the programmer. I will categorize possible intents below, using JSON encoding as a running example.
The programmer wants to handle all fields of the record (since the resulting JSON object should contain them all). [...] If a new field is added, they would like the compiler to warn them that their
toJSONimplementation needs to be updated.The programmer wants to handle all fields of the record, except for a specific subset – for instance, one of the fields can be derived from the rest of the data and should be omitted from the resulting JSON object to keep the representation compact. [...]
3.The programmer wants to handle a subset of fields, and they truly don't care about whatever else the record might contain – for instance, when they know that the resulting JSON will be sent to an endpoint that only inspects certain fields. [...]
W.r.t. the record matching syntax, 1 and 2 are not much different (all fields or all fields except for a specific subset): the programmer should get a warning when a new field is added to the record and his function doesn't inspect it.
I think the original post includes a very significant use case: JSON encoders. In Elm code, we write a lot of these, often encoding records, and it's extremely easy to add something to the record and forget to update its encoder(s).
This could become the syntax for non-exhaustive record matching:
{ a | bar, baz }
a can either be replaced with _ or used as a record containing the fields not listed
Wrt forgetting to update encoders, have you considered writing a rule for elm-review or just using elm-codec?
I've never heard of elm-codec, I'll check it out. I've heard of elm-review but never used it myself. Maybe those tools would be sufficient.
Most helpful comment
Here's a workaround in Elm:
Notice that
encodeFooworks even though it doesn't account for every part of Foo, butsafeEncodeFooproduces the following error:which would prompt the following correction (in this case, intentionally ignoring
baz):