Haskell - 任何限制或消除记录名称的方法?

时间:2011-08-03 06:20:22

标签: haskell record

我有两种数据类型,用于 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)

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

5 个答案:

答案 0 :(得分:12)

正如已经指出的那样,这不是直接可行的,但我想谈谈提议的解决方案:

如果两个字段明显不同,您将始终知道您正在使用哪个字段。在这里,“明显不同”,我的意思是,永远不会有任何情况,在任何一个领域做同样的事情是有意义的。鉴于此,过度的歧义并非真正不受欢迎,因此您需要合格的导入作为标准方法,或者字段消歧扩展,如果这更符合您的口味。或者,作为一个非常简单(并且有点丑陋)的选项,只需手动填写字段前缀,例如deviceArrayName而非name

如果两个字段在某种意义上相同的东西,那么能够以同质的方式对待它们是有意义的;理想情况下,您可以在name字段的选择中编写函数多态。在这种情况下,一个选项是使用类型类作为“命名事物”,其中的函数允许您访问任何适当类型的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) } }

唉。有一些方法可以缓解这个问题,但在我看来,它们仍然不是主意。像这样的情况是我不喜欢一般记录语法的原因。因此,作为最终选项,一些模板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

答案 1 :(得分:5)

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

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

答案 2 :(得分:5)

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

答案 3 :(得分:4)

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

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

答案 4 :(得分:3)

如果要在两者中使用该名称,可以使用定义名称函数的类。 E.g:

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