15

I am a Haskell newbie. I have noticed that Haskell does not support record name overloading:

-- Records.hs

data Employee = Employee
  { firstName :: String
  , lastName :: String
  , ssn :: String
  } deriving (Show, Eq)

data Manager = Manager
  { firstName :: String
  , lastName :: String
  , ssn :: String
  , subordinates :: [Employee]
  } deriving (Show, Eq)

When I compile this I get:

[1 of 1] Compiling Main             ( Records.hs, Records.o )

Records.hs:10:5:
    Multiple declarations of `firstName'
    Declared at: Records.hs:4:5
                 Records.hs:10:5

Records.hs:11:5:
    Multiple declarations of `lastName'
    Declared at: Records.hs:5:5
                 Records.hs:11:5

Records.hs:12:5:
    Multiple declarations of `ssn'
    Declared at: Records.hs:6:5
                 Records.hs:12:5

Given the "strength" of the Haskell type system, it seems like it should be easy for the compiler to determine which field to access in

emp = Employee "Joe" "Smith" "111-22-3333"
man = Manager "Mary" "Jones" "333-22-1111" [emp]
firstName man
firstName emp

Is there some issue that I am not seeing. I know that the Haskell Report does not allow this, but why not?

Ralph
  • 31,584
  • 38
  • 145
  • 282
  • 1
    This is not at all an answer to your question, but I usuaully split the data types into separate modules whenever a situation like yours arises. I might, for example, make an `Employee` module and a `Manager` module, and import them qualified as say `E` and `M` respectively, and then use `E.firstName`, `M.firstName`, etc. This gives me reasonably nice syntax. (I'm not saying this is necessarily a good idea, but it's what I've ended up doing and it's turned out nicely in my cases). – gspr Jun 19 '12 at 13:20
  • 3
    Yeah, but this seems like a "kludge" in an otherwise elegant language. – Ralph Jun 19 '12 at 19:07

3 Answers3

16

Historical reasons. There have been many competing designs for better record systems for Haskell -- so many in fact, that no consensus could be reached. Yet.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 10 years later, is there a consensus yet? [OverloadedRecordDot](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_dot.html) seems nice. – Lynn Sep 09 '22 at 19:50
9

The current record system is not very sophisticated. It's mostly some syntactic sugar for things you could do with boilerplate if there was no record syntax.

In particular, this:

data Employee = Employee
  { firstName :: String
  , lastName :: String
  , ssn :: String
  } deriving (Show, Eq)

generates (among other things) a function firstName :: Employee -> String.

If you also allow in the same module this type:

data Manager = Manager
  { firstName :: String
  , lastName :: String
  , ssn :: String
  , subordinates :: [Employee]
  } deriving (Show, Eq)

then what would be the type of the firstName function?

It would have to be two separate functions overloading the same name, which Haskell does not allow. Unless you imagine that this would implicitly generate a typeclass and make instances of it for everything with a field named firstName (gets messy in the general case, when the fields could have different types), then Haskell's current record system isn't going to be able to support multiple fields with the same name in the same module. Haskell doesn't even attempt to do any such thing at present.

It could, of course, be done better. But there are some tricky problems to solve, and essentially no one's come up with solutions to them that have convinced everyone that there is a most promising direction to move in yet.

Ben
  • 68,572
  • 20
  • 126
  • 174
  • I guess you can create a typeclass and then have the typeclass methods call the specific records versions, but that would add a lot of boilerplate to a language that blessedly usually doesn't need it. – Ralph Jun 19 '12 at 13:05
  • 1
    If you allow field overloading, then the `firstName` function should have the type `forall a. a`. With type inference or explicit type declaration this type should be specialized. Record constructors in Agda works like this. – JJJ Jun 20 '12 at 08:46
4

One option to avoid this is to put your data types in different modules and use qualified imports. In that way you can use the same field accessors on different data records and keep you code clean and more readable.

You can create one module for the employee, for example

module Model.Employee where

data Employee = Employee
  { firstName :: String
  , lastName :: String
  , ssn :: String
  } deriving (Show, Eq)

And one module for the Manager, for example:

module Model.Manager where

import Model.Employee (Employee)

data Manager = Manager
  { firstName :: String
  , lastName :: String
  , ssn :: String
  , subordinates :: [Employee]
  } deriving (Show, Eq)

And then wherever you want to use these two data types you can import them qualified and access them as follows:

import           Model.Employee (Employee)
import qualified Model.Employee as Employee
import           Model.Manager (Manager)
import qualified Model.Manager as Manager

emp = Employee "Joe" "Smith" "111-22-3333"
man = Manager "Mary" "Jones" "333-22-1111" [emp]

name1 = Manager.firstName man
name2 = Employee.firstName emp

Keep in mind that after all you are using two different data types and thus Manger.firstName is another function than Employee.firstName, even when you know that both data types represent a person and each person has a first name. But it is up to you how far you go to abstract data types, for example to create a Person data type from those "attribute collections" as well.

JHannes
  • 1,456
  • 2
  • 10
  • 12