Avoiding namespace pollution in Haskell
Asked Answered
M

5

60

I'm using lots of different records in a program, with some of them using the same field names, e.g.

data Customer = Customer { ..., foo :: Int, ... }
data Product = Product { ..., foo :: Int, ... }

Now as the accessor function "foo" is defined twice, I get the "Multiple declarations" error. One way to avoid this would be using different modules that are imported fully qualified, or simply renaming the fields (which I don't want to do).

What is the officially suggested way of dealing with this in Haskell?

Morion answered 23/11, 2010 at 20:21 Comment(3)
I share your pain. I come from the OO world.Paulson
So it looks like I'll go with the qualified imports -- at least for this project. Thank you all for your answers! This is one of those moments when I miss Scheme macros for getting rid of the DRY violations when using typeclasses...Morion
I have found this project page about OverloadedRecordFields extension for GHC to allow multiple record datatypes to share the same field names.Dolli
K
26

This is a very hairy problem. There are several proposals for fixing the record system. On a related note, see TDNR and related discussion on cafe.

Using the currently available language features, I think the best option is defining the two types in two different modules, and doing a qualified import. On top of this, if you want, you can implement some type class machinery.

In Customer.hs

module Customer where
data Customer = Customer { ..., foo :: Int, ... }

In Product.hs

module Product where
data Product = Product { ..., foo :: Int, ... }

While using them, in Third.hs

module Third where

import qualified Customer as C
import qualified Product as P

.. C.foo ..
.. P.foo ..

Yet, I imagine it won't be too late before you hit the problem about recursively dependent modules.

Karp answered 23/11, 2010 at 23:14 Comment(1)
Good point about the recursive dependencies. But as stated in the linked thread, "I found the lack of this feature to be forcing me to design modules in a tree like structure. And this actually helped me to avoid some bad design. Sometimes [...] I declared mutual dependencies by error - error messages helped be to debug this." So for now I'll stick with fully qualified imports and split my record definitions into multiple files. Maybe go the typeclass approach later, but for now it looks like overkill...Morion
S
13

(FYI, this question is almost certainly a duplicate)

Solutions:

1) Prefix the fields with a tag indicating the type (extremely common)

data Customer = Customer {..., cFoo :: Int, ...}

2) Use type classes (less common, people complain prefixes like cFoo are inconvenient but evidently not so bad that they will write a class and instance or use TH to do the same).

class getFoo a where
    foo :: a -> Int

instance getFoo Customer where
    foo = cFoo

3) Use better field names If the fields are actually different (which isn't always true, my computer has an age as does my employee), then this is the best solution.

Sundin answered 23/11, 2010 at 21:8 Comment(4)
The more common tag syntax, which I use, is c_foo.Rhnegative
The typeclass approach looks promising, but adds a lot of code duplication, as there are many common fields in my data definition (e.g. productId, customerId etc.) that I would need to handle in each typeclass instance again. Instead of renaming I'll rather use the qualified imports to have "real" namespaces.Morion
The type class approach is really unrealistic. It would require creating a separate type class for every named field for which one would like to have common property patterns (getters, setters, modifiers). This is exactly what the record syntax is meant to avoid.Liam
This answer is 4 years old. Certainly one can use Lenses to address most needs these days.Sundin
A
11

There's a language extension DuplicateRecordFields that allows duplication of field functions and makes its type to be inferred by type annotation.

Here is a little example (haskell-stack script):

#!/usr/bin/env stack
-- stack runghc --resolver lts-8.20 --install-ghc

{-# LANGUAGE DuplicateRecordFields #-}

newtype Foo = Foo { baz :: String }
newtype Bar = Bar { baz :: String }

foo = Foo { baz = "foo text" }
bar = Bar { baz = "bar text" }

main = do
  putStrLn $ "Foo: " ++ baz (foo :: Foo) -- Foo: foo text
  putStrLn $ "Bar: " ++ baz (bar :: Bar) -- Bar: bar text
Atomic answered 24/6, 2017 at 12:28 Comment(0)
R
8

See also the Has package: http://chrisdone.com/posts/duck-typing-in-haskell

And if you really need extensible records now, you can always use HList. But I wouldn't recommend this until you're really familiar and comfortable with medium-advanced Haskell, and even then I'd triple check you need it.

Haskelldb has a slightly more lightweight version: http://hackage.haskell.org/packages/archive/haskelldb/2.1.0/doc/html/Database-HaskellDB-HDBRec.html

And then there's another version of extensible records as part of the grapefruit frp library: http://hackage.haskell.org/package/grapefruit-records

Again, for your purposes, I'd bite the bullet and just rename the fields. But these references are to show that when you really need the full power of extensible records, there are ways to do it, even if none are as pleasant as a well-designed language extension would be.

Rhnegative answered 24/11, 2010 at 7:41 Comment(1)
Wow! Just realized how much there really is still to learn for me when it comes to Haskell. For my current project this looks like overkill, but I'll have a closer look at your links.Morion
N
1

One possible solution that will make your code less verbose is to define <.> as:

(<.>) :: (Emiter e1, Emiter e2) => e1 -> e2 -> String
lhs <.> rhs = emit lhs <> emit rhs

Then emitters can look like:

class Emiter n where
    emit :: n -> String 

instance Emiter String where
    emit = id

instance Emiter A where 
    emit A {
        foo = foo'
        bar = bar'
    } = foo' <.> "--" <.> bar'

instance Emiter B where
    emit B {
        foo = foo'
        bar = bar'
    } =  "[" <.> bar' <.> foo' <.> "]"
Nihility answered 10/2, 2019 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.