Haskell记录中的名称冲突

Haskell记录中的名称冲突,haskell,Haskell,Haskell没有记录成员的点符号。编译器为每个记录成员创建一个具有相同名称的函数,其类型为RecType->FieldType。这会导致名称冲突。有没有办法解决这个问题,例如,我如何能有几个字段名相同的记录?对于大型项目,我更喜欢将每种类型保留在自己的模块中,并使用Haskell的模块系统为每种类型命名访问器的名称空间 例如,我可能在模块A中有一些类型A: -- A.hs data A = A { field1 :: String , field2 :: Double

Haskell没有记录成员的点符号。编译器为每个记录成员创建一个具有相同名称的函数,其类型为RecType->FieldType。这会导致名称冲突。有没有办法解决这个问题,例如,我如何能有几个字段名相同的记录?

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

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

-- A.hs

data A = A
    { field1 :: String
    , field2 :: Double
    }
。。。以及另一种类型的
B
,在模块
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没有办法在同一个模块中定义多个名称空间,否则就不需要在单独的模块中拆分每个类型来完成此操作。

避免此问题的另一种方法是使用包。它提供了一个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)

但是我真的可以推荐learning lens,因为它还提供了许多其他实用程序,比如修改或设置字段。

GHC开发人员开发了一些扩展来帮助解决这个问题。退房最初计划了一个
OverloadedRecordFields
扩展,但是开发了两个扩展。扩展是和。另见

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

data Person=MkPerson{personId::Int,name::String}
数据地址=MkAddress{personId::Int,Address::String}

到2019年,我想说的是,这两个扩展没有得到我所认为的采用(尽管它们确实获得了一些采用),而且现状可能仍在继续。

Cf.而且:这可能很快在GHC中得到修复。我认为这是一个GSoC项目:如果两个字段的名称相同,但类型不同,则此项不起作用。@GabrielGonzalez至少镜头版本适用于不同类型。我不想用类型族或函数依赖关系使手动版本复杂化,所以我省略了不同类型的内容。好的,实际上,这并不难,所以我添加了对不同类型的支持。谢谢!我不知道lens版本会这样做。注意:声明的字段名前面应该有驼峰格式的数据类型的名称。例如:data ThingA=ThingA{{u thingAName::String}@MathematicalOrchid它并没有那么笨拙。事实上,这种方法在其他语言中非常常见,例如,在Java中,每个类型(类或接口)都必须位于一个单独的文件中。这个计划发生了什么事?@DerekMahar I用更新的信息更新了答案!谢谢你的更新。你有这个计划的链接吗?“此计划”链接已断开。我再次更新了答案,以删除死链接并添加更多信息。如果您对该功能的历史感兴趣,我建议您查看ghc wiki页面的“历史”部分,以及该页面上的“原始设计”链接。