避免 Haskell 中的名称空间污染

发布于 2024-10-04 05:23:06 字数 285 浏览 8 评论 0原文

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

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

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

Haskell 中处理此问题的官方建议方法是什么?

I'm using lots of different records in a program, with some of them using the same field names, e.g.

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

Now as the accessor function "foo" is defined twice, I get the "Multiple declarations" error. One way to avoid this would be using different modules that are imported fully qualified, or simply renaming the fields (which I don't want to do).

What is the officially suggested way of dealing with this in Haskell?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

绝不服输 2024-10-11 05:23:06

这是一个非常棘手的问题。有几个修复记录系统的建议。有关相关说明,请参阅 TDNRcafe 相关讨论.

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

在 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 ..

然而,我想在您遇到有关 递归依赖模块

This is a very hairy problem. There are several proposals for fixing the record system. On a related note, see TDNR and related discussion on cafe.

Using the currently available language features, I think the best option is defining the two types in two different modules, and doing a qualified import. On top of this, if you want, you can implement some type class machinery.

In Customer.hs

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

In Product.hs

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

While using them, in Third.hs

module Third where

import qualified Customer as C
import qualified Product as P

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

Yet, I imagine it won't be too late before you hit the problem about recursively dependent modules.

遗忘曾经 2024-10-11 05:23:06

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

解决方案:

1)在字段前添加一个指示类型的标签前缀(非常常见)

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

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

class getFoo a where
    foo :: a -> Int

instance getFoo Customer where
    foo = cFoo

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

(FYI, this question is almost certainly a duplicate)

Solutions:

1) Prefix the fields with a tag indicating the type (extremely common)

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

2) Use type classes (less common, people complain prefixes like cFoo are inconvenient but evidently not so bad that they will write a class and instance or use TH to do the same).

class getFoo a where
    foo :: a -> Int

instance getFoo Customer where
    foo = cFoo

3) Use better field names
If the fields are actually different (which isn't always true, my computer has an age as does my employee), then this is the best solution.

伴随着你 2024-10-11 05:23:06

有一个语言扩展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

There's a language extension DuplicateRecordFields that allows duplication of field functions and makes its type to be inferred by type annotation.

Here is a little example (haskell-stack script):

#!/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
左耳近心 2024-10-11 05:23:06

另请参阅 Has 包: 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

再次,为了您的目的,我会硬着头皮重命名字段。但这些参考资料表明,当您确实需要可扩展记录的全部功能时,有很多方法可以做到这一点,即使没有一种方法像精心设计的语言扩展那样令人愉快。

See also the Has package: http://chrisdone.com/posts/duck-typing-in-haskell

And if you really need extensible records now, you can always use HList. But I wouldn't recommend this until you're really familiar and comfortable with medium-advanced Haskell, and even then I'd triple check you need it.

Haskelldb has a slightly more lightweight version: http://hackage.haskell.org/packages/archive/haskelldb/2.1.0/doc/html/Database-HaskellDB-HDBRec.html

And then there's another version of extensible records as part of the grapefruit frp library: http://hackage.haskell.org/package/grapefruit-records

Again, for your purposes, I'd bite the bullet and just rename the fields. But these references are to show that when you really need the full power of extensible records, there are ways to do it, even if none are as pleasant as a well-designed language extension would be.

牵强ㄟ 2024-10-11 05:23:06

一种可能的解决方案是,将 <.> 定义为:

(<.>) :: (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' <.> "]"

One possible solution that will make your code less verbose is to define <.> as:

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

Then emitters can look like:

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' <.> "]"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文