Предположим, что у меня есть два типа данных Foo и Bar. Foo имеет поля x и y. Бар имеет поля x и z. Я хочу иметь возможность написать функцию, которая принимает либо Foo, либо Bar в качестве параметра, извлекает значение x, выполняет некоторые вычисления на нем, а затем возвращает новый Foo или Bar с установленным значением x.Синтаксис и типы классов записей Haskell
Вот один подход:
class HasX a where
getX :: a -> Int
setX :: a -> Int -> a
data Foo = Foo Int Int deriving Show
instance HasX Foo where
getX (Foo x _) = x
setX (Foo _ y) val = Foo val y
getY (Foo _ z) = z
setY (Foo x _) val = Foo x val
data Bar = Bar Int Int deriving Show
instance HasX Bar where
getX (Bar x _) = x
setX (Bar _ z) val = Bar val z
getZ (Bar _ z) = z
setZ (Bar x _) val = Bar x val
modifyX :: (HasX a) => a -> a
modifyX hasX = setX hasX $ getX hasX + 5
Проблема заключается в том, что все эти методы получения и установки больно писать, особенно если заменить Foo и Bar с типами данных в реальном мире, которые имеют много полей.
Синтаксис записи Haskell дает намного лучший способ определения этих записей. Но, если я пытаюсь определить записи, как этого
data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show
Я получаю ошибку о том, что х определяются несколько раз. И я не вижу никакого способа сделать эту часть класса типа, чтобы передать их в updateX.
Есть ли хороший чистый способ решить эту проблему, или я застрял в определении моих собственных геттеров и сеттеров? Иными словами, существует ли способ подключения функций, созданных синтаксисом записи, к классам классов (как для геттеров, так и для сеттеров)?
EDIT
Вот реальная проблема, которую я пытаюсь решить. Я пишу серию связанных программ, которые используют System.Console.GetOpt для анализа своих параметров командной строки. В этих программах будет много опций командной строки, но некоторые из них могут иметь дополнительные опции. Я хочу, чтобы каждая программа могла определять запись, содержащую все ее значения параметров. Затем я начинаю с значения записи по умолчанию, которое затем преобразуется через монаду StateT и GetOpt, чтобы получить окончательную запись, отражающую аргументы командной строки. Для одной программы этот подход работает очень хорошо, но я пытаюсь найти способ повторного использования кода во всех программах.
Если у вас есть один тип данных 'data FooBar = Foo {x :: Int, y :: Int} | Bar {x :: Int, z :: Int} 'у вас не было бы этой проблемы. Если ваши типы данных были в разных модулях, вы могли бы использовать '{- # LANGUAGE DisambiguateRecordFields # -}'. Какие-либо причины придерживаться вашего нынешнего дизайна? – ephemient