2010-11-23 17 views
51

私はプログラム内でたくさんの異なるレコードを使用していますが、そのうちのいくつかは同じフィールド名を使用しています。Haskellでの名前空間汚染の回避

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

アクセサー関数「foo」が2回定義されているため、「複数の宣言」エラーが発生します。これを避ける1つの方法は、完全修飾されたインポートされた別のモジュールを使用するか、フィールドの名前を変更するだけです(これはやりたくない)。

ハスケルでこれを処理する正式な方法は何ですか?

+3

私はあなたの痛みを共有:

はここで少し例(haskell-stackスクリプト)です。私はOOの世界から来ています。 – gawi

+2

少なくとも、このプロジェクトでは、資格のある輸入品を使って行くようです。あなたの答えはありがとう!これは、私がtypeclassesを使うときにDRY違反を取り除くためのSchemeマクロを見逃したその瞬間のひとつです... – lbruder

+1

[このプロジェクトページ](https://ghc.haskell.org/trac/ghc/wiki/)が見つかりました。レコード/ OverloadedRecordFields)は、複数のレコードデータ型が同じフィールド名を共有できるようにGHCのための 'OverloadedRecordFields'拡張機能に関するものです。 – Alexey

答えて

22

これは非常に毛深い問題です。レコードシステムを固定するためのいくつかの提案があります。関連するノートについては、TDNRrelated discussion on cafeを参照してください。

現在使用可能な言語機能を使用して、最良の選択肢は、2つの異なるモジュールで2つのタイプを定義し、修飾されたインポートを行うことです。これに加えて、必要ならば、いくつかの型クラスの機械を実装することができます。 Customer.hsで

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

Product.hs

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

しかしThird.hs

module Third where 

import qualified Customer as C 
import qualified Product as P 

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

で、それらを使用している間、私はそれができません想像します遅すぎると遅くともrecursively dependent modulesの問題が発生します。

+0

再帰的依存関係についての良い点。しかし、リンクされたスレッドで述べたように、「このような機能がないと、ツリーのようなモジュールでモジュールを設計することになってしまいましたが、実際には悪い設計を避けるために役立ちました。エラーによって - これをデバッグするのに役立つエラーメッセージ。 "ですから、今は完全修飾のインポートを行い、レコード定義を複数のファイルに分割します。たぶん、後でtypeclassのアプローチに行くかもしれませんが、今のところそれは過度のように見えます... – lbruder

12

(FYI、この質問はほぼ確実に重複している)

ソリューション:

1)プレフィックスタグが付けられたフィールドタイプ(非常に共通)

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

2を示す)を使用します(あまり一般的でない人は、cFooのようなプレフィックスに不便ですが、明らかにクラスやインスタンスを書くか、または同じことをするためにTHを使います)。フィールドが)私のコンピュータは私の従業員が行うように年齢を持って、常に真実ではないとする(実際には異なる場合

class getFoo a where 
    foo :: a -> Int 

instance getFoo Customer where 
    foo = cFoo 

3) より良いフィールド名を使用して、これが最善の解決策です。

+0

私が使用する、より一般的なタグ構文はc_fooです。 – sclv

+0

typeclassのアプローチは有望ですが、データの定義(productId、customerIdなど)には、それぞれのタイプインスタンスで再度処理する必要がある共通フィールドが多数存在するため、コードの重複が多く発生します。名前を変更する代わりに、正規のインポートを使用して「本当の」名前空間を使用します。 – lbruder

+0

タイプクラスのアプローチは本当に非現実的です。共通のプロパティパターン(getter、setters、modifiers)を持つ名前付きフィールドごとに別のタイプクラスを作成する必要があります。これはまさにレコード構文が避けるべき意味です。 – ely

7

も持つパッケージを参照してください:http://chrisdone.com/posts/duck-typing-in-haskell

をそして今、あなたは本当に必要拡張可能なレコードの場合、あなたは常にHListを使用することができます。しかし、中級のハスケルに慣れ親しんで快適になるまで、私はこれをお勧めしません。

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

アゲインフィールドの名前を変更するだけです。しかし、これらの参考文献は、あなたが実際に拡張可能なレコードのフルパワーを必要とするときに、うまく設計された言語拡張が楽しいものでなくても、それを行う方法があることを示しています。

+0

うわー!それがハスケルになると私にとって本当にどれくらいのことが学べるのかが分かりました。私の現在のプロジェクトでは、これは過剰なもののようですが、あなたのリンクを詳しく見ていきます。 – lbruder

2

フィールド機能の複製を許可し、型のアノテーションによってその型を推論できるようにする言語拡張子DuplicateRecordFieldsがあります。

#!/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 
関連する問題