Подтвердить что ты не робот

Конфликты имен в записях Haskell

Haskell не имеет точечной нотации для участников записи. Для каждого элемента записи компилятор создает функцию с тем же именем с типом RecType → FieldType. Это приводит к конфликтам имен. Есть ли способ обойти это, т.е. Как я могу иметь несколько записей с одинаковыми именами полей?

4b9b3361

Ответ 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 год я бы сказал, что эти два расширения не получили усыновления, которое, как я думал, они получат (хотя они и получили некоторое усыновление), и статус-кво, вероятно, все еще продолжается.