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

[ bsd3, development, program ] [ Propose Tags ]

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]
Versions 0.1, 0.1.1, 0.1.2, 0.1.3, 0.1.4
Change log CHANGES.txt
Dependencies base (>=4.6 && <5), extra, filepath [details]
License BSD-3-Clause
Copyright Neil Mitchell 2018
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 Fri Sep 7 13:44:12 UTC 2018
Distributions LTSHaskell:0.1.4, NixOS:0.1.4, Stackage:0.1.4
Executables record-dot-preprocessor
Downloads 250 total (53 in the last 30 days)
Rating (no votes yet) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
Last success reported on 2018-09-07 [all 3 reports]
Hackage Matrix CI

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for record-dot-preprocessor-0.1.4

[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 Haskell. 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 add {-# OPTIONS_GHC -F -pgmF=record-dot-preprocessor #-} to the top of the file. Suddenly your records will work. You must make sure that the preprocessor is applied both to the file where your records are defined, and where the record syntax is used.

The resulting program will require GHC 8.2 or above and the lens library, or a module called Control.Lens exporting the contents of Lens.Micro from microlens (which has significantly less dependencies).

What magic is available, precisely?

  • e.b, where e is an expression (not a constructor) and there are no whitespace on either side of the ., is translated to a record lookup. If you want to use the standard . function composition operator, insert a space. If you want to use a qualfied module name, then e will look like a constructor, so it won't clash.
  • e{b = c} is a record update. Provided the record was defined in a module where record-dot-preprocessor was used, the meaning will be equivalent to before. If you want to use a normal unchanged record update, insert a space before the {.
  • e{b * c}, where * is an arbitrary operator, is equivalent to e{b = e.b * c}. If you want to apply an arbitrary function as c, use the & operator. Think e.b *= c in C-style languages.
  • e.b.c{d.e * 1, f.g = 2} also works and all variants along those lines.

I don't believe in magic, what's the underlying science?

On the way back from ZuriHac 2017 Neil Mitchell and Mathieu Boespflug were discussing lenses and the sad state of records in Haskell. We both agreed that overloaded labels should be defined such that they resolve to lenses. With the right instances, you could define a ^. #foo to get the foo field from the expression a. This preprocessor just turns a.foo into a ^. #foo, and generates the right instances. If you really want to see the magic under the hood simply run record-dot-preprocessor yourfile.hs and it will print out what it generates.

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.