Вопрос

Предположим, что у меня есть два типа данных Foo и Bar.В Foo есть поля x и y.Бар имеет поля x и z.Я хочу иметь возможность написать функцию, которая принимает в качестве параметра либо Foo, либо Bar, извлекает значение x, выполняет над ним некоторые вычисления, а затем возвращает новый Foo или Bar с соответствующим установленным значением x.

Вот один из подходов:

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

Я получу сообщение об ошибке, сообщающее, что x определен несколько раз.И я не вижу никакого способа сделать их частью класса типа, чтобы можно было передать их в модифицирующийX.

Есть ли хороший и чистый способ решения этой проблемы, или я застрял в определении своих собственных геттеров и сеттеров?Другими словами, есть ли способ связать функции, созданные с помощью синтаксиса записи, с классами типов (как геттерами, так и сеттерами)?

РЕДАКТИРОВАТЬ

Вот настоящая проблема, которую я пытаюсь решить.Я пишу серию связанных программ, все из которых используют System.Console.GetOpt для анализа параметров командной строки.В этих программах будет много параметров командной строки, общих для всех этих программ, но некоторые из них могут иметь дополнительные параметры.Я бы хотел, чтобы каждая программа могла определять запись, содержащую все значения ее параметров.Затем я начинаю со значения записи по умолчанию, которое затем преобразуется с помощью монады StateT и GetOpt, чтобы получить окончательную запись, отражающую аргументы командной строки.Для одной программы этот подход работает очень хорошо, но я пытаюсь найти способ повторно использовать код во всех программах.

Это было полезно?

Решение

Вы хотите расширяемые записи как я понимаю, это одна из самых обсуждаемых тем в Haskell.Похоже, что в настоящее время нет единого мнения о том, как его реализовать.

В вашем случае кажется, что вместо обычной записи вы могли бы использовать гетерогенный список, подобный тем, которые реализованы в HList.

Опять же, похоже, у вас здесь только два уровня:общие и программные.Поэтому, возможно, вам следует просто определить общий тип записи для общих параметров и тип записи, специфичный для каждой программы, и использовать StateT для кортежа этих типов.Для общих целей вы можете добавить псевдонимы, которые составляют fst с общими средствами доступа, чтобы они были невидимы для вызывающих абонентов.

Другие советы

Вы можете использовать такой код, как

data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show)
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show)

instance HasX Foo where
  getX = fooX
  setX r x' = r { fooX = x' }

instance HasX Bar where
  getX = barX
  setX r x' = r { barX = x' }

Что вы моделируете в своем коде?Если бы мы знали больше о проблеме, мы могли бы предложить что-то менее неудобное, чем этот объектно-ориентированный дизайн, втиснутый в функциональный язык.

Мне кажется, это работа для дженериков.Если бы вы могли пометить свой Int разными новыми типами, вы могли бы написать (с помощью uniplate, модуль PlateData):

data Foo = Foo Something Another deriving (Data,Typeable)
data Bar = Bar Another Thing deriving (Data, Typerable)

data Opts = F Foo | B Bar

newtype Something = S Int
newtype Another = A Int
newtype Thing = T Int

getAnothers opts = [ x | A x <- universeBi opts ]

Это позволит извлечь всех Других из любого места внутри Opts.

Модификация также возможна.

Если вы сделаете типы экземплярами Foldable, вы получите функцию toList, которую можно использовать в качестве основы для вашего средства доступа.

Если Foldable вам ничего не дает, то, возможно, правильный подход — определить нужный вам интерфейс как класс типа и найти хороший способ автоматической генерации производных значений.

Возможно, исходя из выполнения

deriving(Data)

вы можете использовать комбинаторы gmap для обоснования доступа.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top