record-dot-preprocessor
Preprocessor to allow record.field syntax
https://github.com/ndmitchell/record-dot-preprocessor#readme
LTS Haskell 23.1: | 0.2.17 |
Stackage Nightly 2024-12-09: | 0.2.17 |
Latest on Hackage: | 0.2.17 |
record-dot-preprocessor-0.2.17@sha256:fdc8038505aef4f5bf5decae3d08143b614facda44bc78e2270fcee2be116675,2560
Module documentation for 0.2.17
record-dot-preprocessor
In almost every programming language a.b
will get the b
field from the a
data type, and many different data types can have a b
field. The reason this feature is ubiquitous is because it’s useful. The record-dot-preprocessor
brings this feature to modern GHC versions. This feature has been proposed for Haskell as RecordDotSyntax
. Since GHC 9.2 the OverloadedRecordDot
and OverloadedRecordUpdate
extensions implement much the same functionality. Some examples:
data Company = Company {name :: String, owner :: Person}
data Person = Person {name :: String, age :: Int}
display :: Company -> String
display c = c.name ++ " is run by " ++ c.owner.name
nameAfterOwner :: Company -> Company
nameAfterOwner c = c{name = c.owner.name ++ "'s Company"}
Here we declare two records both with name
as a field, then write c.name
and c.owner.name
to get those fields. We can also write c{name = x}
as a record update, which still works even though name
is no longer unique.
How do I use this magic?
First install record-dot-preprocessor
with either stack install record-dot-preprocessor
or cabal update && cabal install record-dot-preprocessor
. Then at the top of the file add:
- Either:
{-# OPTIONS_GHC -F -pgmF=record-dot-preprocessor #-}
for the preprocessor. - Or:
{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}
and{-# LANGUAGE DuplicateRecordFields, TypeApplications, FlexibleContexts, DataKinds, MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances, UndecidableInstances, GADTs #-}
for the GHC plugin.
The GHC plugin only runs on GHC 8.6 or higher, has some issues on Windows and has much better error messages. In contrast, the preprocessor runs everywhere and has more features.
You must make sure that the OPTIONS_GHC
is applied both to the file where your records are defined, and where the record syntax is used. The resulting program will require the record-hasfield
library.
What magic is available, precisely?
Using the preprocessor or the GHC plugin you can write:
expr.lbl
is equivalent togetField @"lbl" expr
(the.
cannot have whitespace on either side).expr{lbl = val}
is equivalent tosetField @"lbl" expr val
(the{
cannot have whitespace before it).(.lbl)
is equivalent to(\x -> x.lbl)
(the.
cannot have whitespace after).
Using the preprocessor, but not the GHC plugin:
expr{lbl1.lbl2 = val}
is equivalent toexpr{lbl1 = (expr.lbl1){lbl2 = val}}
, performing a nested update.expr{lbl * val}
is equivalent toexpr{lbl = expr.lbl * val}
, where*
can be any operator.expr{lbl1.lbl2}
is equivalent toexpr{lbl1.lbl2 = lbl2}
.
These forms combine to offer the identities:
expr.lbl1.lbl2
is equivalent to(expr.lbl1).lbl2
.(.lbl1.lbl2)
is equivalent to(\x -> x.lbl1.lbl2)
.expr.lbl1{lbl2 = val}
is equivalent to(expr.lbl1){lbl2 = val}
.expr{lbl1 = val}.lbl2
is equivalent to(expr{lbl1 = val}).lbl2
.expr{lbl1.lbl2 * val}
is equivalent toexpr{lbl1.lbl2 = expr.lbl1.lbl2 * val}
.expr{lbl1 = val1, lbl2 = val2}
is equivalent to(expr{lbl1 = val1}){lbl2 = val2}
.
How does this magic compare to other magic?
Records in Haskell are well known to be pretty lousy. There are many proposals that aim to make Haskell records more powerful using dark arts taken from type systems and category theory. This preprocessor aims for simplicity - combining existing elements into a coherent story. The aim is to do no worse than Java, not achieve perfection.
Any advice for using this magic?
The most important consideration is that all records used by a.b
or a{b=c}
syntax must have HasField
instances, which requires either running the preprocessor/plugin over the module defining them, or writing orphan instances by hand. To use records which don’t have such instances use normal selector functions (e.g. b a
) and insert a space before the {
(e.g. a {b=c}
).
Limitations
- The preprocessor doesn’t deal with anti-quoted expressions inside
QuasiQuotes
, e.g.[D.pgSQL|$ SELECT ${dummy.x} :: text|]
.
Changes
0.2.17, released 2024-01-13
Support GHC 9.8
#59, support GHC 9.6
0.2.16, released 2023-03-03
#57, support GHC 9.4
#54, skip polymorphic fields with forall
#52, properly deal with nested block comments
0.2.15, released 2022-07-09
#50, support GHC 9.2
#50, add ghc version bounds
0.2.14, released 2022-02-11
#48, do not derive HasField for existentials
0.2.13, released 2021-11-03
#46, make sure [a|b/|] gets treated as quasi quotes
0.2.12, released 2021-09-01
#45, re-export preprocessor internals as library
0.2.11, released 2021-05-28
#41, use qualified names in the plugin
0.2.10, released 2021-03-01
#40, compatibility with qualified QuasiQuotes
Emit LINE pragmas slightly earlier in some cases
#37, do a better job at HLint clean
0.2.9, released 2021-02-27
#37, make the output HLint clean
Don't add the OverloadedLabels extension
0.2.8, released 2021-02-21
Support GHC 9.0
#38, make the preprocessor avoid quasi quotes
0.2.7, released 2020-10-02
#29, deal with records containing type families in field types
0.2.6, released 2020-08-12
#30, don't warn about incomplete record updates
#31, allow fields to have names that clash with functions
0.2.5, released 2020-05-06
#28, deal with kind signatures on data types
0.2.4, released 2020-05-04
#3, emit more LINE declarations
0.2.3, released 2020-04-01
Support GHC 8.10
0.2.2, released 2019-12-08
#26, make a {b=c} not desugar to setField
0.2.1, released 2019-11-02
#25, support promoted data kinds, e.g. 'Int
#12, support more things around GADTs
Make sure the plugin errors on update{}
0.2, released 2019-03-29
Add a GHC source plugin
Support for e{foo.bar}
Support for (.foo.bar)
a.b{c=d} now equivalent to (a.b){c=d}, previously was a{b.c=d}
0.1.5, released 2019-02-09
#10, support fields named 'x'
0.1.4, released 2018-09-07
Licensed under BSD-3-Clause OR Apache-2.0
0.1.3, released 2018-07-26
Give a unique name to each _preprocessor_unused
0.1.2, released 2018-07-26
Make qualified types in records work
Add LINE droppings to get approximate line numbers correct
Don't depend on anything not imported from Control.Lens
0.1.1, released 2018-05-09
Handle - as an update operator
Be compatible with qualified imports
0.1, released 2018-05-06
Initial version