28

Haskell 没有记录成员的点符号。对于每个记录成员,编译器创建一个具有相同名称且类型为 RecType -> FieldType 的函数。这会导致名称冲突。有什么办法可以解决这个问题,即我怎样才能有多个具有相同字段名称的记录?

4

3 回答 3

22

对于大型项目,我更喜欢将每种类型保留在自己的模块中,并使用 Haskell 的模块系统来命名每种类型的访问器。

例如,我可能A在 module 中有一些类型A

-- A.hs

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

B...以及在 module 中具有类似名称字段的另一种类型B

-- B.hs

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

然后,如果我想在其他模块中使用这两种类型,C我可以导入它们,以区分我的意思是哪个访问器:

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

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

不幸的是,Haskell 没有办法在同一个模块中定义多个命名空间,否则就不需要将每个类型拆分到一个单独的模块中来执行此操作。

于 2013-07-05T01:32:42.407 回答
18

避免此问题的另一种方法是使用镜头包。它提供了一个 makeFields 模板 haskell 函数,你可以像这样使用它:

{-# 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)

如果您不想使用 TemplateHaskell 和 lens,您也可以手动执行 lens 使用 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)

但我真的可以推荐学习镜头,因为它还提供了许多其他实用程序,例如修改或设置字段。

于 2013-07-05T11:57:29.777 回答
9

GHC 开发人员开发了几个扩展来帮助解决这个问题。查看这个 ghc wiki 页面。最初计划进行一个OverloadedRecordFields扩展,但开发了两个扩展。扩展是OverloadedLabelsDuplicateRecordFields。另请参阅reddit 讨论

DuplicateRecordFields 扩展使此代码在单个模块中合法:

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

截至 2019 年,我想说这两个扩展没有得到我认为他们会采用的采用(尽管它们确实得到了一些采用)并且现状可能仍在进行中。

于 2013-10-09T08:00:03.200 回答