Name conflicts in Haskell records

For large projects, I prefer to keep each type in its own module and use Haskell's module system to namespace accessors for each type.

For example, I might have some type A in module A:

-- A.hs

data A = A
    { field1 :: String
    , field2 :: Double
    }

... and another type B with similarly-named fields in module B:

-- B.hs

data B = B
    { field1 :: Char
    , field2 :: Int
    }

Then if I want to use both types in some other module C I can import them qualified to distinguish which accessor I mean:

-- C.hs
import A as A
import B as B

f :: A -> B -> (Double, Int)
f a b = (A.field2 a, B.field2 b)

Unfortunately, Haskell does not have a way to define multiple name-spaces within the same module, otherwise there would be no need to split each type in a separate module to do this.


Another way to avoid this problem is to use the lens package. It provides a makeFields template haskell function, which you can use like this:

{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE TemplateHaskell        #-}
{-# LANGUAGE TypeSynonymInstances   #-}
import           Control.Lens

data A = A
  { _aText :: String
  }
makeFields ''A   -- Creates a lens x for each record accessor with the name _aX

data B = B
  { _bText  :: Int
  , _bValue :: Int
  }
-- Creates a lens x for each record accessor with the name _bX
makeFields ''B  

main = do
  let a = A "hello"
  let b = B 42 1

  -- (^.) is a function of lens which accesses a field (text) of some value (a)
  putStrLn $ "Text of a: " ++ a ^. text 
  putStrLn $ "Text of b: " ++ show (b ^. text)

If you don't want to use TemplateHaskell and lens, you can also do manually what lens automates using TemplateHaskell:

{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE TypeSynonymInstances   #-}
data A = A
  { aText :: String
  }

data B = B
  { bText  :: Int
  , bValue :: Int
  }

-- A class for types a that have a "text" field of type t
class HasText a t | a -> t where

  -- An accessor for the text value
  text :: a -> t

-- Make our two types instances of those
instance HasText A String where text = aText
instance HasText B Int where text = bText

main = do
  let a = A "hello"
  let b = B 42 1
  putStrLn $ "Text of a: " ++ text a
  putStrLn $ "Text of b: " ++ show (text b)

But I can really recommend learning lens, as it also provides lots of other utilities, like modifying or setting a field.


The GHC developers developed a couple of extensions to help with this issue . Check out this ghc wiki page. Initially a single OverloadedRecordFields extension was planned, but instead two extensions were developed. The extensions are OverloadedLabels and DuplicateRecordFields. Also see that reddit discussion.

The DuplicateRecordFields extensions makes this code legal in a single module:

data Person  = MkPerson  { personId :: Int, name :: String }
data Address = MkAddress { personId :: Int, address :: String }

As of 2019, I'd say these two extensions didn't get the adoption I thought they would have (although they did get some adoption) and the status quo is probably still ongoing.

Tags:

Haskell