在Haskell中有记录的替代方案吗?

时间:2014-01-03 20:32:09

标签: haskell

我正在寻找标准记录的更好替代方案,这些记录只是自动生成的元组上的访问器函数。问题是命名问题,其中具有相同名称的字段的记录具有相同的访问者功能。

6 个答案:

答案 0 :(得分:6)

我正在使用lens和makeFields TH函数。这让我做这样的事情:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data Person = Person { _personFirstName :: String
                     , _personLastName :: String
                     , _personEmail :: String }

data Corp = Corp { _corpName :: String
                 , _corpEmail :: String }

makeFields ''Person
makeFields ''Corp

main = 
  let myGuy = Person "Test" "Guy" "email@account.com" 
      myCorp = Corp "ABC" "sales@abc.com" in
      putStrLn $ "personal email: " ++ (myGuy^.email) ++
                 "corp email: " ++ (myCorp^.email) 

这里发生的是makeFields函数创建具有单个成员(名称或电子邮件)的类型类HasName和HasEmail。它还为这些类型创建实例。 MultiParamTypeClasses和Functional Dependencies使得可以为不同的数据类型“重载”这些成员。

按惯例,

makeFields将为以下划线开头的每个记录构造函数执行此操作,并且它将为该字段选择的名称是从第一个大写字母开始的构造函数名称的余额。所以_corpEmail会生成一个“电子邮件”字段。

现在这些“领域”都是镜头,而且你可以应用很多functions。要简单地从记录中获取值,您可以使用“查看”功能,例如:

view email myGuy

这个函数的反函数称为(^。),它将记录作为第一个参数,将镜头作为第二个参数。在很多情况下我个人更喜欢这种风格,但我有时也会使用视图,特别是在无点风格中。

Lens比这个功能更大 - 我个人觉得这个功能引人注目,但它真正为你做的最重要的事情是让你能够组合这些镜头来深入了解数据结构。使用(。)运算符组合就像任何其他函数一样;因此,如果我们想象我的示例有_corpPresident :: Person字段,我可以这样做:

 putStrLn $ myCorp^.president.email 

或 - 更改此嵌套值:

let newCorp = set president.email "changedemail@address.com" myCorp 

或同样的事情(更多操作员疯狂):

let newCorp = myCorp & president.email .~ "changedemail@address.com"

还有更多,我自己也几乎没有抓到表面。 hackage文档很好,但我认为最好的入门方法是使用Github自述文件中的字段指南repository

答案 1 :(得分:5)

我的命名空间冲突政策是将冲突类型放在单独的模块中。然后,您可以使用Haskell的命名空间工具来解决冲突。但是,我希望Haskell能够在单个模块中定义多个名称空间。

答案 2 :(得分:4)

来自Haskel 98 report

  

使用字段标签的操作在3.15节中描述。数据声明可以在多个构造函数中使用相同的字段标签,只要在类型同义词扩展之后所有情况下字段的键入都相同。标签不能由范围内的多个类型共享。字段名称与普通变量和类方法共享顶级名称空间,并且不得与范围中的其他顶级名称冲突。

所以不,没有办法绕过它。

我经常做类似

的事情
data Person = Person
    { personName :: String
    , personAge :: Int
    } deriving (Eq, Show)

data Pet = Pet
    { petName :: String
    , petAge :: Int
    } deriving (Eq, Show)

class Living a where
    name :: a -> String
    age :: a -> Int

instance Living Person where
    name = personName
    age = personAge

instance Living Pet where
    name = petName
    age = petAge

但说实话,我很少需要(或使用)类似的类型。它确实显示了如何在不同的记录类型上使用相同的“访问者”。

答案 3 :(得分:1)

Vinyl库引入了一个解决方案。引用作者:

  

Vinyl是Haskell中使用类型级别字符串和其他现代GHC功能的记录问题的一般解决方案,具有静态结构类型(具有子类型关系)和自动行多态透镜。如果没有Template Haskell,这一切都是可能的。

然而,如果内存占用对你来说很重要,我建议选择加布里埃尔的提议,因为黑胶唱片更重要。

总的来说,记录问题是一个臭名昭着的问题,自90年代末以来就提出了很好的解决方案(你能想象吗?)。但是,由于实施它们变得困难,没有人能够做到这一点。最近我发起了a related discussion on Reddit,你可以在那里得到一些有趣的信息。事实证明,当时正在做一些工作,至少涉及命名空间问题,预计它的结果很快就能看到世界。

答案 4 :(得分:1)

最简单的方法是启用DisambiguateRecordFields扩展程序。这根本没有太大变化,它基本上使用Haskell98数据声明,但允许您重用字段名称。我发现它工作得非常好,特别是如果你更进一步RecordWildCards

答案 5 :(得分:0)

使用DisambiguateRecordFields的NamedFieldPuns,但这仅限于函数并且允许绑定,而不是任意表达式: http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/syntax-extns.html