【问题标题】:Haskell -- any way to qualify or disambiguate record names?Haskell - 任何方式来限定或消除记录名称的歧义?
【发布时间】:2011-10-18 20:13:57
【问题描述】:

我有两种数据类型,用于 hastache 模板。在我的代码中,有两种不同的类型是有意义的,它们都有一个名为“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)

即如果这些字段现在在代码中使用,它们将是“DeviceArray.name”和“MakefileParams.name”?

【问题讨论】:

  • this,虽然有点老了……不知道有没有人知道“更好的记录提案”的状态是什么?

标签: haskell record


【解决方案1】:

如前所述,这不是直接可能的,但我想就提议的解决方案说几件事:

如果这两个字段明显不同,那么您总是想知道自己在使用哪个字段。这里的“明显不同”是指永远不会有对任何一个领域做同样的事情是有意义的情况。鉴于此,过度消歧并不是不受欢迎的,因此您希望将 qualified imports 作为标准方法,或者如果这更符合您的口味,则希望使用 field disambiguation 扩展。或者,作为一个非常简单(而且有点难看)的选项,只需手动为字段添加前缀,例如deviceArrayName 而不仅仅是 name

如果这两个字段在某种意义上相同,那么能够以同质的方式对待它们是有意义的;理想情况下,您可以编写一个选择name 字段的多态函数。在这种情况下,一种选择是使用 type class 来表示“命名的事物”,其功能可以让您访问任何适当类型的 name 字段。这里的一个主要缺点是,除了琐碎的类型约束的扩散和可怕的单态限制可能令人头疼的问题之外,您还失去了使用记录语法的能力,这开始破坏整个观点。

我还没有看到建议的类似字段的另一个主要选项是将name 字段提取到单个参数化类型中,例如data Named a = Named { name :: String, item :: a }GHC itself uses this approach for source locations in syntax trees,虽然它不使用记录语法,但想法是一样的。这里的缺点是,如果您有Named DeviceArray,现在访问bytes 字段需要经过两层记录。如果你想用函数更新bytes 字段,你会遇到这样的事情:

addBytes b na = na { item = (item na) { bytes = b + bytes (item na) } }

呃。有一些方法可以稍微缓解这个问题,但在我看来,它们仍然不知道。像这样的情况是我一般不喜欢记录语法的原因。所以,作为最后的选择,一些 Template Haskell 魔法和the fclabels package:

{-# LANGUAGE TemplateHaskell #-}

import Control.Category
import Data.Record.Label

data Named a = Named 
    { _name :: String, 
      _namedItem :: a }
    deriving (Eq, Show, Data, Typeable)

data DeviceArray = DeviceArray { _bytes :: Int }
    deriving (Eq, Show, Data, Typeable)

data MakefileParams = MakefileParams { _makefileParams :: [MakeParam] }
    deriving (Eq, Show, Data, Typeable)

data MakeParam = MakeParam { paramText :: String }
    deriving (Eq, Show, Data, Typeable)

$(mkLabels [''Named, ''DeviceArray, ''MakefileParams, ''MakeParam])

不要介意MakeParam 业务,我只是需要一个字段来做点什么。无论如何,现在您可以像这样修改字段:

addBytes b = modL (namedItem >>> bytes) (b +)
nubParams = modL (namedItem >>> makefileParams) nub

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

【讨论】:

  • 总的来说,这看起来是一个非常好的解决方案,但我认为它不适用于我的特殊情况:hastache 使用 Data.Generics “反射”,所以我希望实际的字段名称成为“名字”。也许这个问题与具有某种“联合”记录类型的更普遍的问题有关,即避免您上面提到的“项目”字段。
  • @gatoatigrado:您可以使用任何您喜欢的字段名称。如果它被称为name,fclabels 将生成一个名为lName 的镜头。如果您只使用镜头,下划线会更好。你仍然会有嵌套记录的烦恼,这会让事情变得更加丑陋。顺便说一句,这是我对基于反射的技术持谨慎态度的一个很好的例子。对无关语法的神奇的非本地依赖使代码变得脆弱且不可组合。
  • @gatoatigrado:此外,您考虑的一般问题基本上最终会导致某种结构子类型化。这是个好主意,但我的印象是,要很好地实现它是相当棘手的。
  • 应该注意这个答案现在已经过时了。 GHC 扩展 -XDuplicateRecordFields 完全实现了 OP 在 GHC 8.0 中的要求(我认为是一些早期版本)。
【解决方案2】:

记录字段名称与数据类型在同一范围内,因此您不能直接这样做。

解决此问题的常用方法是为字段名称添加前缀,例如daNamempName,或者将它们放在单独的模块中,然后 import qualified

【讨论】:

    【解决方案3】:

    您可以做的是将每种数据类型放在其自己的模块中,然后您可以使用合格的导入来消除歧义。这有点笨拙,但它确实有效。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 对不起,我没有在问题中明确提及;我已经意识到了这一点,不幸的是它只会清除不同模块中不同数据类型对相同字段名称的引用,而我关心的是在同一模块中定义两个。
      【解决方案5】:

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

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-27
        • 1970-01-01
        • 1970-01-01
        • 2018-08-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多