Haskell——有什么方法来限定或消除记录名的歧义吗?

Haskell——有什么方法来限定或消除记录名的歧义吗?,haskell,record,Haskell,Record,我有两种数据类型,用于hastache模板。在我的代码中,有两种不同的类型是有意义的,它们都有一个名为“name”的字段。当然,这会引起冲突。似乎有一种机制可以消除对“name”的任何调用的歧义,但实际的定义会导致问题。有没有什么解决办法,比如让记录字段名被限定 data DeviceArray = DeviceArray { name :: String, bytes :: Int } deriving (Eq, Show, Data, Typeable) dat

我有两种数据类型,用于hastache模板。在我的代码中,有两种不同的类型是有意义的,它们都有一个名为“name”的字段。当然,这会引起冲突。似乎有一种机制可以消除对“name”的任何调用的歧义,但实际的定义会导致问题。有没有什么解决办法,比如让记录字段名被限定

data DeviceArray = DeviceArray
    { name :: String,
      bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

data TemplateParams = TemplateParams
    { arrays :: [DeviceArray],
      input :: DeviceArray }
    deriving (Eq, Show, Data, Typeable)

data MakefileParams = MakefileParams
    { name :: String }
    deriving (Eq, Show, Data, Typeable)
i、 e.如果这些字段现在在代码中使用,它们将是“DeviceArray.name”和“MakefileParams.name”?

有几个可能会有所帮助。链接的一个适用于您的情况


或者,您可以重构代码并对记录中的公共字段使用TypeClass。或者,您应该手动为每个记录选择器添加前缀。

记录字段名称与数据类型在同一范围内,因此不能直接执行此操作


解决这一问题的常用方法是在字段名中添加前缀,例如
daName
mpName
,或者将它们放在单独的模块中,然后导入符合条件的
import

可以做的是将每个数据类型放在自己的模块中,然后使用符合条件的导入消除歧义。虽然有点笨重,但它确实有效。

如前所述,这是不可能直接实现的,但我想就建议的解决方案说几句话:

如果这两个字段明显不同,您将始终希望知道您使用的是哪一个字段。我这里所说的“明显不同”是指,在任何情况下,对任何一个领域做同样的事情都是没有意义的。有鉴于此,过度的歧义消除并不是真的不受欢迎,因此您可能希望使用合格的导入作为标准方法,或者如果更符合您的口味,则使用字段消歧扩展。或者,作为一个非常简单(而且有点难看)的选项,只需手动为字段添加前缀,例如,
deviceArrayName
,而不只是
name

如果这两个领域在某种意义上是相同的东西,那么能够以一种同质的方式对待它们是有意义的;理想情况下,您可以在选择
name
字段中编写一个多态函数。在本例中,一个选项是为“命名的事物”使用类型类,其函数允许您访问任何适当类型上的
name
字段。这里的一个主要缺点是,除了琐碎的类型约束和可怕的单态约束可能带来的麻烦外,您还失去了使用记录语法的能力,这开始破坏整个要点

类似字段的另一个主要选项是将
name
字段提取为一个参数化类型,例如
data Named a=Named{name::String,item::a}
,我还没有看到建议,虽然它没有使用记录语法,但想法是一样的。这里的缺点是,如果您有一个名为DeviceArray的
,那么访问
字节
字段现在需要经过两层记录。如果要使用函数更新
字节
字段,则必须执行以下操作:

addBytes b na = na { item = (item na) { bytes = b + bytes (item na) } }
addBytes b = modL (namedItem >>> bytes) (b +)
nubParams = modL (namedItem >>> makefileParams) nub
啊。有一些方法可以稍微缓解这个问题,但在我看来,这些方法仍然是不可行的。像这样的情况就是我通常不喜欢记录语法的原因。因此,作为最后一个选项,一些模板Haskell magic和:

不要介意
MakeParam
业务,我只是需要一个字段来处理它。无论如何,现在您可以修改如下字段:

addBytes b na = na { item = (item na) { bytes = b + bytes (item na) } }
addBytes b = modL (namedItem >>> bytes) (b +)
nubParams = modL (namedItem >>> makefileParams) nub

您还可以将
bytes
命名为类似
bytesInternal
的名称,然后根据需要导出访问器
bytes=namedItem>>bytesInternal

如果您想在两者中都使用名称,可以使用定义名称函数的类。例如:

Class Named a where
    name :: a -> String

data DeviceArray = DeviceArray
    { deviceArrayName :: String,
      bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

instance Named DeviceArray where
    name = deviceArrayName

data MakefileParams = MakefileParams
    { makefileParamsName :: String }
    deriving (Eq, Show, Data, Typeable)

instance Named MakefileParams where
    name = makefileParamsName

然后你可以在这两个类中使用
name

虽然有点旧,但是有。。。我想知道是否有人知道“更好的记录提案”的状态是什么?总体而言,这看起来是一个非常好的解决方案,但我认为它不适用于我的特殊情况:hastache使用数据。泛型是“反射”,所以我希望实际字段名是“名称”。也许这个问题与更一般的问题有关,即使用某种“联合”记录类型,即避免使用上面提到的“项目”字段。@gatoatigrado:您可以使用任何您喜欢的字段名。如果名为
name
,fclabels将生成名为
lName
的镜头。如果只使用镜头,下划线会更好。您仍然会有嵌套记录的烦恼,这会使Hastach中的内容更加丑陋。顺便说一句,这是一个很好的例子,说明了为什么我对基于反射的技术持谨慎态度;对不相关语法的神奇非局部依赖使代码变得脆弱和非组合。@gatoatigrado:另外,您所考虑的一般问题基本上会导致某种结构子类型。这是一个好主意,但我的印象是,很好地实施它是相当棘手的。应该注意的是,这个答案现在已经过时了。GHC扩展名
-XDuplicateRecordFields
完全实现了OP在GHC 8.0(我认为是一些早期版本)中所要求的功能;我已经意识到了这一点,不幸的是,它只清除了不同模块中不同数据类型对相同字段名的引用,而我关心的是在同一模块中定义两个字段名。