When you write business-logic in Haskell, you oftentimes have some intermediate results in your long-ish functions that are n-tuples of mostly the same type, or maybe a Map Text Text or a Either (Either Foo Foo) Foo.
Of course you can always create newtype wrappers for everything, but what if there was a generalized way to do that for simple cases. Well there is!
When we just need a simple “label” for the types we are dealing with, we can use the code above. So the Map Text Text above would become something like Map (Label "objectId" Text) (Label "description Text). And all related values will show these annotated types as well, making it harder to accidentally confuse values.
As an added benefit, via the HasField instance we can just use dot syntax to unwrap the values again! And it uses the label name!! This is pretty much the best possible interface imho.
The only missing piece would be a pattern synonym for unwrapping the label, but I haven’t figured out how to do that yet while also requiring the label name to be written in the pattern match. Something like
case obj of
Left (Label @"objectId" objId) -> …
…
would be ideal, but I don’t think it’s possible to require the type argument to be there.
Other than that, pretty neat, I think we are gonna use that a lot.
Oh, and since it’s a newtype it’s also a so-called “zero cost abstraction”. Fancy!
(Background: we originally used superrecord, which is a full-blown anonymous record library, but noticed it had a few bugs & triggered GHC bugs as well, so this gives us 80% of the benefits without any of the complicated stuff)
