Home > Software engineering >  Solutions for "multiple declarations" in Haskell sum data types?
Solutions for "multiple declarations" in Haskell sum data types?

Time:11-20

GHC 9.2.4 gives a multiple declaration error for the following code:

data X = A | B | C
data Y = A | B | C

There are so many new extensions in GHC nowadays. Is there one that allows me to do the above (since it is semantically appropriate for my problem domain) or is the common solution still to prefix, like data X = XA | XB | XC ?

CodePudding user response:

You should prefix them, or put them in separate modules. And probably you should also use them this explicitly qualified way.

However if you insist, you can fake constructors that are polymorphic to work for both cases, by using a type class and constructing patterns synonyms over it. In this case, the easiest way is to use the fact that both of these are suitable to X:

{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}

class HasABCConstructors w where
  fromX :: X -> w
  toX :: w -> X

instance HasABCConstructors X where
  fromX = id
  toX = id

instance HasABCConstructors Y where
  fromX XA = YA
  fromX XB = YB
  fromX XC = YC
  toX YA = XA
  toX YB = XB
  toX YC = XC

pattern A :: HasABCConstructors w => w
pattern A <- (toX -> XA)
 where A = fromX XA
ghci> A :: X
XA
ghci> A :: Y
YA

CodePudding user response:

Even without any extensions, you can declare the two types in different modules. You say in a comment "I wish these things can be namespaced"; the module system is namespaces.

module X where

data X = A | B | C
module Y where

data Y = A | B | C
module OnlyUsesX where

import X

foo :: Char -> Maybe X
foo 'a' = Just A
foo 'b' = Just B
foo 'c' = Just C
foo _ = Nothing
module UsesBoth where

import X (X)             -- import only the type name unqualified, since
                         -- it's unambiguous
import qualified X as X  -- also import the entire module qualified, for
                         -- the ambiguous stuff

import Y (Y)
import qualified Y as Y -- the "as Y" is pointless here, but normally the
                        -- full module name is longer; if you use the type
                        -- name as the module alias, then it looks like you
                        -- have a namespace associated with the type, much
                        -- like OO code

foo :: X -> Y -> String
foo X.A Y.A = "both A"
foo X.B Y.B = "both B"
foo _ _ = "I can't be bothered enumerating the rest of the possibilities"

If you put your types in small modules dedicated to just that one type, then in other modules where you use multiple types you can refer to the types as simply X or Y, and constructors/fields of the type as X.whatever or Y.whatever.

This lets you use short prefixless names in the constructor/field names themselves (which avoids the prefixes infecting derived read/show/json/etc instances, if you care about that). Where there isn't any usage of conflicting names you have the option of importing unqualified and dropping the prefixes, which you wouldn't have if the prefix was baked into the actual constructor/field names. And it also avoids any ambiguous inference problems that a hypothetical version of GHC would necessarily have if it allowed duplicate constructor/pattern names in the same namespace and tried to use types to resolve which is which; if you ever use conflicting imported names in the same namespace the compiler will tell you and you can just use the module prefix to resolve it for the benefit of both the compiler and any human readers.

  • Related