Haskell не имеет точечной нотации для участников записи. Для каждого элемента записи компилятор создает функцию с тем же именем с типом RecType → FieldType. Это приводит к конфликтам имен. Есть ли способ обойти это, т.е. Как я могу иметь несколько записей с одинаковыми именами полей?
Конфликты имен в записях Haskell
Ответ 1
Другим способом избежать этой проблемы является использование пакета lens. Он предоставляет функцию haskell шаблона makeFields, которую вы можете использовать следующим образом:
{-# 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 и объектив, вы также можете вручную вручную настроить объектив с помощью 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)
Но я действительно рекомендую использовать объектив для обучения, так как он также предоставляет множество других утилит, таких как изменение или настройка поля.
Ответ 2
Для больших проектов я предпочитаю хранить каждый тип в отдельном модуле и использовать систему модулей Haskell для доступа к пространству имен для каждого типа.
Например, у меня может быть какой-то тип A
в модуле A
:
-- A.hs
data A = A
{ field1 :: String
, field2 :: Double
}
... и другой тип B
с одноименными полями в модуле 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 нет способа определить несколько пространств имен в одном и том же модуле, иначе для этого не нужно было бы разбивать каждый тип на отдельный модуль.
Ответ 3
Разработчики GHC разработали несколько расширений, чтобы помочь с этой проблемой. Проверьте эту страницу ghc wiki. Первоначально было запланировано одно расширение OverloadedRecordFields
, но вместо этого были разработаны два расширения. Расширения: OverloadedLabels и DuplicateRecordFields. Также смотрите это обсуждение Reddit.
Расширения DuplicateRecordFields делают этот код легальным в одном модуле:
data Person = MkPerson { personId :: Int, name :: String }
data Address = MkAddress { personId :: Int, address :: String }
По состоянию на 2019 год я бы сказал, что эти два расширения не получили усыновления, которое, как я думал, они получат (хотя они и получили некоторое усыновление), и статус-кво, вероятно, все еще продолжается.