record-dot-preprocessor: Preprocessor to allow record.field syntax

[ bsd3, development, library, program ] [ Propose Tags ] [ Report a vulnerability ]

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 Haskell - see the README for full details.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1, 0.1.1, 0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.2, 0.2.1, 0.2.2, 0.2.3, 0.2.4, 0.2.5, 0.2.6, 0.2.7, 0.2.8, 0.2.9, 0.2.10, 0.2.11, 0.2.12, 0.2.13, 0.2.14, 0.2.15, 0.2.16, 0.2.17
Change log CHANGES.txt
Dependencies base (>=4.8 && <5), extra, ghc (>=8.6 && <9.9), uniplate [details]
Tested with ghc ==9.8, ghc ==9.6, ghc ==9.4, ghc ==9.2, ghc ==9.0, ghc ==8.10, ghc ==8.8
License BSD-3-Clause
Copyright Neil Mitchell 2018-2024
Author Neil Mitchell <ndmitchell@gmail.com>
Maintainer Neil Mitchell <ndmitchell@gmail.com>
Category Development
Home page https://github.com/ndmitchell/record-dot-preprocessor#readme
Bug tracker https://github.com/ndmitchell/record-dot-preprocessor/issues
Source repo head: git clone https://github.com/ndmitchell/record-dot-preprocessor.git
Uploaded by NeilMitchell at 2024-01-13T21:56:44Z
Distributions NixOS:0.2.17, Stackage:0.2.17
Reverse Dependencies 5 direct, 2 indirect [details]
Executables record-dot-preprocessor
Downloads 11533 total (122 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
All reported builds failed as of 2024-01-13 [all 1 reports]

Readme for record-dot-preprocessor-0.2.17

[back to package description]

record-dot-preprocessor Hackage version Stackage version Build status

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 to getField @"lbl" expr (the . cannot have whitespace on either side).
  • expr{lbl = val} is equivalent to setField @"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 to expr{lbl1 = (expr.lbl1){lbl2 = val}}, performing a nested update.
  • expr{lbl * val} is equivalent to expr{lbl = expr.lbl * val}, where * can be any operator.
  • expr{lbl1.lbl2} is equivalent to expr{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 to expr{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|].