避免Haskell中的命名空间污染

时间:2010-11-23 20:21:09

标签: haskell namespaces types records

我在程序中使用了很多不同的记录,其中一些使用相同的字段名称,例如

data Customer = Customer { ..., foo :: Int, ... }
data Product = Product { ..., foo :: Int, ... }

现在由于访问器函数“foo”被定义了两次,我得到“多个声明”错误。避免这种情况的一种方法是使用完全限定导入的不同模块,或者只是重命名字段(我不想这样做)。

在Haskell中正式建议的处理方法是什么?

5 个答案:

答案 0 :(得分:23)

这是一个非常毛病的问题。 修复记录系统有几个建议。在相关说明中,请参阅TDNRrelated discussion on cafe

使用当前可用的语言功能,我认为最好的选择是在两个不同的模块中定义两种类型,并进行合格的导入。除此之外,如果需要,您可以实现一些类型类机制。

在Customer.hs

module Customer where
data Customer = Customer { ..., foo :: Int, ... }

在Product.hs中

module Product where
data Product = Product { ..., foo :: Int, ... }

使用它们时,在Third.hs中

module Third where

import qualified Customer as C
import qualified Product as P

.. C.foo ..
.. P.foo ..

然而,我想在你遇到关于recursively dependent modules的问题之前不会太迟。

答案 1 :(得分:12)

(仅供参考,这个问题几乎肯定是重复的)

解决方案:

1)使用指示类型(非常常见)的标签前缀字段

data Customer = Customer {..., cFoo :: Int, ...}

2)使用类型类(不太常见,像cFoo这样的人抱怨前缀不方便,但显然不是很糟糕,以至于他们会写一个类和实例,或者使用TH来做同样的事情。)

class getFoo a where
    foo :: a -> Int

instance getFoo Customer where
    foo = cFoo

3)使用更好的字段名称 如果字段实际上不同(这并非总是如此,我的计算机的年龄与我的员工一样),那么这是最佳解决方案。

答案 2 :(得分:7)

另请参阅包装:http://chrisdone.com/posts/duck-typing-in-haskell

如果你现在真的需要可扩展记录,你总是可以使用HList。但是在你对中等先进的Haskell非常熟悉和熟悉之前我不会推荐这个,即便如此,我还要三重检查你是否需要它。

Haskelldb的版本稍微轻一些:http://hackage.haskell.org/packages/archive/haskelldb/2.1.0/doc/html/Database-HaskellDB-HDBRec.html

然后还有另一个版本的可扩展记录作为葡萄柚frp库的一部分:http://hackage.haskell.org/package/grapefruit-records

再次,为了您的目的,我会咬紧牙关,只是重命名字段。但是这些参考文献表明,当你真正需要可扩展记录的全部功能时,有很多方法可以做到,即使没有一个方法可以像设计良好的语言扩展那样令人愉快。

答案 3 :(得分:6)

有一个语言扩展DuplicateRecordFields,允许复制字段函数,并使其类型由类型注释推断。

这是一个小例子(haskell-stack脚本):

#!/usr/bin/env stack
-- stack runghc --resolver lts-8.20 --install-ghc

{-# LANGUAGE DuplicateRecordFields #-}

newtype Foo = Foo { baz :: String }
newtype Bar = Bar { baz :: String }

foo = Foo { baz = "foo text" }
bar = Bar { baz = "bar text" }

main = do
  putStrLn $ "Foo: " ++ baz (foo :: Foo) -- Foo: foo text
  putStrLn $ "Bar: " ++ baz (bar :: Bar) -- Bar: bar text

答案 4 :(得分:0)

一种可能使您的代码不那么冗长的解决方案是将<.>定义为:

(<.>) :: (Emiter e1, Emiter e2) => e1 -> e2 -> String
lhs <.> rhs = emit lhs <> emit rhs

然后发射器看起来像:

class Emiter n where
    emit :: n -> String 

instance Emiter String where
    emit = id

instance Emiter A where 
    emit A {
        foo = foo'
        bar = bar'
    } = foo' <.> "--" <.> bar'

instance Emiter B where
    emit B {
        foo = foo'
        bar = bar'
    } =  "[" <.> bar' <.> foo' <.> "]"