Name conflicts in Haskell records

前端 未结 3 1425
生来不讨喜
生来不讨喜 2020-12-08 04:41

Haskell doesn\'t have dot notation for record members. For each record member a compiler creates a function with the same name with a type RecType -> FieldType. This leads

相关标签:
3条回答
  • 2020-12-08 05:10

    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.

    0 讨论(0)
  • 2020-12-08 05:18

    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.

    0 讨论(0)
  • 2020-12-08 05:24

    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.

    0 讨论(0)
提交回复
热议问题