May 31, 2021
Written by Artyom Kazak
-XRecordWildCards
are very convenient. Let's say you need to write a ToJSON
instance manually (maybe you need to modify some of the fields):
data Rec = Rec { foo :: Int, bar :: Int, qux :: Int }
instance ToJSON Rec where
toJSON Rec{..} = object [
"foo" .= foo,
"bar" .= bar,
"qux" .= qux ]
There is just one problem. If you add a field to Rec
, the compiler will not warn you, and your ToJSON
instance will potentially miss a field. Boo! Static guarantees. We want static guarantees. We want mindless refactorings.
(AFAIK Rust got it right. But I might be misremembering.)
Another case is when you want to do a pairwise operation on all or most fields of two records, like this:
diffRec :: Rec -> Rec -> SomeFancyDiff
diffRec a b = ...
-XRecordWildCards
doesn't even help here — you get name clashes — and GHC is not going to tell you that diffRec
should be updated when Rec
is updated.
I have just released a small library, safe-wild-cards
, that lets us have safer wildcard matches at the cost of somewhat worse syntax.
Instead of Rec{..}
, write $(fields 'Rec)
:
{-# LANGUAGE TemplateHaskell #-}
import SafeWildCards
data Rec = Rec { foo :: Int, bar :: Int, qux :: Int }
$(pure []) -- see https://blog.monadfix.com/th-groups for the explanation of this
instance ToJSON Rec where
toJSON $(fields 'Rec) = object [
"foo" .= foo,
"bar" .= bar,
"qux" .= qux ]
Under the hood, $(fields 'Rec)
expands into Rec foo bar qux
. Same code, but now GHC will warn you when you add a field and forget to either use it or explicitly ignore it.
The warning will look like this:
example.hs:20:5: warning: [-Wunused-matches]
Defined but not used: ‘newField’
|
20 | toJSON $(fields 'Rec) = ...
| ^^^^^^^^^^^^
If you have more than one record of the same type, you can use fieldsPrefixed
:
diffRec :: Rec -> Rec -> SomeFancyDiff
diffRec $(fieldsPrefixed "a_" 'Rec) $(fieldsPrefixed "b_" 'Rec) =
diff a_foo b_foo <> diff a_bar b_bar <> ...
If there is demand for this kind of compile-time safety, maybe eventually it will result in a new GHC warning.
I had a GHC ticket somewhere, but lost it — if anybody else thinks it's a good idea, please file a new one!