Składnia rekordów Haskella i klasy typów

Załóżmy, że mam dwa typy danych Foo i Bar. Foo ma pola x i y. Pasek ma pola x i z. Chcę móc napisać funkcję, która pobiera parametr Foo lub Bar, wyodrębnia wartość x, wykonuje na niej obliczenia, a następnie zwraca nową wartość Foo lub Bar z odpowiednio ustawioną wartością x.

Oto jedno podejście:

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

Problem polega na tym, że wszystkie te programy pobierające i ustawiające są bolesne do napisania, zwłaszcza jeśli zastąpię Foo i Bar typami danych rzeczywistych, które mają wiele pól.

Składnia rekordów Haskella daje o wiele ładniejszy sposób definiowania tych rekordów. Ale jeśli spróbuję zdefiniować takie rekordy

data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show

Pojawi się błąd mówiący, że x jest zdefiniowany wiele razy. I nie widzę żadnego sposobu, aby uczynić te części klasy typu, aby móc przekazać je do modyfikacjiX.

Czy istnieje miły, czysty sposób rozwiązania tego problemu, czy też utknąłem przy definiowaniu własnych getterów i setterów? Innymi słowy, czy istnieje sposób łączenia funkcji utworzonych przez składnię rekordów z klasami typów (zarówno pobierającymi, jak i ustawiającymi)?

EDYTOWAĆ

Oto prawdziwy problem, który próbuję rozwiązać. Piszę serię powiązanych programów, z których wszystkie używają System.Console.GetOpt do analizowania swoich opcji wiersza poleceń. Będzie wiele opcji wiersza polecenia, które są wspólne dla tych programów, ale niektóre programy mogą mieć dodatkowe opcje. Chciałbym, aby każdy program mógł zdefiniować rekord zawierający wszystkie jego wartości opcji. Następnie zaczynam od domyślnej wartości rekordu, która jest następnie przekształcana przez monadę StateT i GetOpt, aby uzyskać ostatni rekord odzwierciedlający argumenty wiersza polecenia. W przypadku pojedynczego programu podejście to działa naprawdę dobrze, ale staram się znaleźć sposób na ponowne użycie kodu we wszystkich programach.

questionAnswers(4)

yourAnswerToTheQuestion