2009-12-13 2 views
11

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

+3

Если у вас есть один тип данных 'data FooBar = Foo {x :: Int, y :: Int} | Bar {x :: Int, z :: Int} 'у вас не было бы этой проблемы. Если ваши типы данных были в разных модулях, вы могли бы использовать '{- # LANGUAGE DisambiguateRecordFields # -}'. Какие-либо причины придерживаться вашего нынешнего дизайна? – ephemient

ответ

5

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

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

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

+0

Интересная идея о том, что у вас есть общий тип записи и тип программы. У меня может быть больше групп общности, а не просто общая и программа (еще не уверен), но я мог бы легко расширить ваш подход, чтобы поддержать это. (Например, я мог бы передавать ассоциативный список записей через StateT, и каждый вариант знал бы, как искать соответствующую запись в списке по имени.) –

3

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

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' } 

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

+0

Спасибо за ответ. Ваш подход несколько упрощает. Я изменил свой первоначальный вопрос, чтобы больше объяснить реальную проблему, которую я пытаюсь решить. Я согласен с тем, что использование подхода OO с функциональным языком обычно не является лучшим способом. Я новичок в функциональных языках и все еще пытаюсь вырваться из своего мышления ОО. :-) –

1

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

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

Возможно, выводя делать

deriving(Data) 

можно использовать GMAP комбинаторы базировать ваш доступ выключен.

2

Мне кажется, как работа для дженериков. Если бы вы могли помечать 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 ] 

Это извлечет все Чужие из любой точки внутри КЛЮЧ.

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

Смежные вопросы