28

Haskell には、レコード メンバーのドット表記がありません。各レコード メンバーに対して、コンパイラは同じ名前の関数を RecType -> FieldType 型で作成します。これにより、名前の競合が発生します。これを回避する方法はありますか。つまり、同じフィールド名を持つ複数のレコードを作成するにはどうすればよいですか?

4

3 に答える 3

22

大規模なプロジェクトでは、各タイプを独自のモジュールに保持し、Haskell のモジュール システムを使用して各タイプの名前空間アクセサーを使用することを好みます。

たとえばA、 module にA次のような型があるとします。

-- A.hs

data A = A
    { field1 :: String
    , field2 :: Double
    }

... およびBmodule に同様の名前のフィールドを持つ別の型B:

-- B.hs

data B = B
    { field1 :: Char
    , field2 :: Int
    }

次に、他のモジュールで両方のタイプを使用したい場合は、Cそれらを修飾してインポートして、どのアクセサーを意味するかを区別できます。

-- C.hs
import A as A
import B as B

f :: A -> B -> (Double, Int)
f a b = (A.field2 a, B.field2 b)

残念ながら、Haskell には同じモジュール内で複数の名前空間を定義する方法がありません。それ以外の場合、これを行うために各タイプを個別のモジュールに分割する必要はありません。

于 2013-07-05T01:32:42.407 に答える
18

この問題を回避する別の方法は、レンズパッケージを使用することです。次のように使用できる makeFields テンプレート haskell 関数を提供します。

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

data A = A
  { _aText :: String
  }
makeFields ''A   -- Creates a lens x for each record accessor with the name _aX

data B = B
  { _bText  :: Int
  , _bValue :: Int
  }
-- Creates a lens x for each record accessor with the name _bX
makeFields ''B  

main = do
  let a = A "hello"
  let b = B 42 1

  -- (^.) is a function of lens which accesses a field (text) of some value (a)
  putStrLn $ "Text of a: " ++ a ^. text 
  putStrLn $ "Text of b: " ++ show (b ^. text)

TemplateHaskell と lens を使用したくない場合は、TemplateHaskell を使用してレンズが自動化するものを手動で行うこともできます。

{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE TypeSynonymInstances   #-}
data A = A
  { aText :: String
  }

data B = B
  { bText  :: Int
  , bValue :: Int
  }

-- A class for types a that have a "text" field of type t
class HasText a t | a -> t where

  -- An accessor for the text value
  text :: a -> t

-- Make our two types instances of those
instance HasText A String where text = aText
instance HasText B Int where text = bText

main = do
  let a = A "hello"
  let b = B 42 1
  putStrLn $ "Text of a: " ++ text a
  putStrLn $ "Text of b: " ++ show (text b)

しかし、フィールドの変更や設定など、他の多くのユーティリティも提供するので、lens を学習することを強くお勧めします。

于 2013-07-05T11:57:29.777 に答える
9

GHC の開発者は、この問題を解決するためにいくつかの拡張機能を開発しました。このghc wikiページをチェックしてください。当初は 1 つのOverloadedRecordFields拡張機能が計画されていましたが、代わりに 2 つの拡張機能が開発されました。拡張機能はOverloadedLabelsDuplicateRecordFieldsです。reddit のディスカッションも参照してください。

DuplicateRecordFields 拡張機能により、このコードは単一のモジュールで有効になります。

data Person  = MkPerson  { personId :: Int, name :: String }
data Address = MkAddress { personId :: Int, address :: String }

2019 年の時点で、これら 2 つの拡張機能は、私が思っていたほどには採用されていないと思います (ただし、ある程度は採用されました)。現状はおそらくまだ続いています。

于 2013-10-09T08:00:03.200 に答える