I am looking for a better alternative to the standard records which are just auto generated accessor functions over tuples. The problem is the naming issues where records with the same names of their fields get same accessor functions.
-
1Most people who are tired of haskell records move on to Lens: http://hackage.haskell.org/package/lens – user2407038 Jan 03 '14 at 20:48
-
@user2407038 It however does not solve the issue of name conflicts. – Nikita Volkov Jan 04 '14 at 00:26
-
@NikitaVolkov It does solve it if you use classy lenses, e.g. see my answer [here](http://stackoverflow.com/a/20910825/546084). However, it suffers from the same problems as [bheklilr's answer](http://stackoverflow.com/a/20912296/546084), namely it introduces type classes solely to get overloading, and it adds a bit of extra boilerplate (though not much - the majority of it can be auto-generated with Template Haskell). – Chris Taylor Jan 04 '14 at 01:16
6 Answers
I am using lens with the makeFields TH function. This lets me do something like this:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Person = Person { _personFirstName :: String
, _personLastName :: String
, _personEmail :: String }
data Corp = Corp { _corpName :: String
, _corpEmail :: String }
makeFields ''Person
makeFields ''Corp
main =
let myGuy = Person "Test" "Guy" "email@account.com"
myCorp = Corp "ABC" "sales@abc.com" in
putStrLn $ "personal email: " ++ (myGuy^.email) ++
"corp email: " ++ (myCorp^.email)
What is happening here is that the makeFields function creates type-classes HasName and HasEmail that have a single member (name, or email). It also creates instances for them for these types. MultiParamTypeClasses and Functional Dependencies make it possible to "overload" these members for different data types.
makeFields by convention is going to do this for each record constructor that begins with an underscore, and the name it will choose for the field is the balance of the constructor name beginning at the first capital letter. So _corpEmail makes an "email" field.
Now these "fields" are lenses, and there are a lot of functions that you can apply with them. To simply get the value from a record you could use the "view" function, e.g.:
view email myGuy
There is an inverse of this function called (^.), which takes a record as the first parameter and the lens as the second. I personally prefer this style in many cases but I still use view sometimes as well, particularly in a points-free style.
Lens is a LOT bigger than just this feature - I personally find this feature compelling but really the most important thing it does for you is enabling you to compose these lenses to make updates deep in data structures. Composition is simply with the (.) operator like any other function; so if we imagine my example had a _corpPresident :: Person
field, I could do this:
putStrLn $ myCorp^.president.email
Or - to change this nested value :
let newCorp = set president.email "changedemail@address.com" myCorp
Or same thing (with more operator madness) :
let newCorp = myCorp & president.email .~ "changedemail@address.com"
There is a lot more and I've hardly scratched the surface myself. The hackage docs are good but I think the best way to get started is with the field guide which is in the readme of the Github repository.

- 1,198
- 1
- 9
- 16
-
Can you break things down a bit with a few comments on how this works and what the cap-dot operator does/is. – Trident D'Gao Jan 06 '14 at 01:21
-
My policy for namespace conflicts is to put conflicting types in separate modules. Then you can use Haskell's namespacing tools to resolve conflicts. However, I wish Haskell had a way to define multiple namespaces within a single module.

- 34,863
- 3
- 77
- 135
-
3All of these answers are outdated since GHC 8 release, which added DuplicateRecordFields extension: https://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html#duplicate-record-fields – Jeremy Nov 05 '17 at 16:02
From the Haskel 98 report
Operations using field labels are described in Section 3.15. A data declaration may use the same field label in multiple constructors as long as the typing of the field is the same in all cases after type synonym expansion. A label cannot be shared by more than one type in scope. Field names share the top level namespace with ordinary variables and class methods and must not conflict with other top level names in scope.
So no, there isn't a way around it.
I usually do something like
data Person = Person
{ personName :: String
, personAge :: Int
} deriving (Eq, Show)
data Pet = Pet
{ petName :: String
, petAge :: Int
} deriving (Eq, Show)
class Living a where
name :: a -> String
age :: a -> Int
instance Living Person where
name = personName
age = personAge
instance Living Pet where
name = petName
age = petAge
But to be honest I rarely need (or use) a type class like that. It does show how you can use the same "accessor" on different record types.

- 53,530
- 6
- 107
- 163
-
6This is a good example of what the typeclasses were not meant for, i.e. overloading. Also the amount of introduced boilerplate makes one wonder whether it's at all worth it. The prefixing of fieldnames with the name of record, as in "Person -> personName", however is a popular convention. An alternative approach was mentioned by Gabriel, and I for one would rather prefer it. – Nikita Volkov Jan 04 '14 at 00:21
-
@NikitaVolkov I agree, this is a poor use of a typeclass, it was simply there to show OP how to have accessors of the same name for different record types. I would not recommend doing this, and using different modules is often better, but if your application has lots of small record types this gets just as cumbersome and difficult to use. – bheklilr Jan 04 '14 at 00:56
A Vinyl library introduces one solution. To quote the authors:
Vinyl is a general solution to the records problem in Haskell using type level strings and other modern GHC features, featuring static structural typing (with a subtyping relation), and automatic row-polymorphic lenses. All this is possible without Template Haskell.
If however the memory footprint is of concern for you, I'd suggest going with Gabriel's proposal, since Vinyl records are more heavyweight.
In general, the Records problem is a notorious issue and great solutions to it have been proposed since the late 90s (can you imagine?). But since implementing them turned out to be hard, noone ever got to it. Recently I initiated a related discussion on Reddit, you can get some interesting info there. Turns out, some work is being done at the time at least concerning the namespacing problem and the results of it are expected to see the world some time soon.

- 42,792
- 11
- 94
- 169
The simplest way around it is to enable the DisambiguateRecordFields
extension. That doesn't change much at all, it uses basically Haskell98 data declarations but allows you to re-use field names. I've found it to work quite nicely, particularly if you go one step further to RecordWildCards
.

- 117,950
- 5
- 174
- 319
-
NamedFieldPuns is safer and almost as powerful/concise as RecordWildcards – misterbee Jan 04 '14 at 17:30
-
How is it "safer"? It's more explicit, if you mean that. As long as you don't use `{..}` `RecordWildcards` doesn't do anything that might cause trouble, so I'd consider it perfectly safe. – leftaroundabout Jan 04 '14 at 18:01
-
RecordWildcards without `{..}` is DisambiguateRecordFields, so you may as well use DisambiguateRecordFields explicitly to be safe. (Or is there another difference?).http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/syntax-extns.html – misterbee Jan 04 '14 at 18:31
NamedFieldPuns with DisambiguateRecordFields, but that is limited to function and let bindings, not arbitrary expressions: http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/syntax-extns.html

- 5,142
- 3
- 25
- 34