Существуют ли значения по умолчанию для средств получения записей в Haskell?

Неудивительно, что следующий код вызывает исключение во время выполнения:

data Necklace = InvalidNecklace |
    Necklace { necklace_id :: Int, meow :: Int, ... }
necklace_id InvalidNecklace

Есть ли какой-то естественный способ определить значение для necklace_id при применении к InvalidNecklace, чтобы получить значение, а не генерировать исключение?

GHC терпит неудачу с ошибкой множественных объявлений для `necklace_id', если я попытаюсь сделать очевидную вещь:

necklace_id InvalidNecklace = -1

Возможно, есть какая-то прагма, которая скажет GHC заменить предполагаемое объявление этим объявлением?

Я мог бы объявить InvalidNecklace записью, добавив { necklace_id :: Int }, но, честно говоря, я не могу гарантировать, что она всегда возвращает -1 и обычно создает ужасный беспорядок. Я мог бы просто определить:

get_necklace_id InvalidNecklace = -1
get_necklace_id x = necklace_id x

но это частично противоречит цели записей.

Я полагаю, что можно создать специальное значение invalidNecklace, написав:

invalidNecklace = Necklace { necklace_id = -1,
     meow = error "meow invalidNecklace accessed", ... }

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


person Jeff Burdges    schedule 25.01.2012    source источник
comment


Ответы (2)


(ОБНОВЛЕНО НИЖЕ)

Как вы обнаружили, геттер, определенный объявлением Necklace, не может быть определен далее. Нет никакой прагмы, чтобы изменить это.

Обычной практикой в ​​Haskell было бы использование этого стиля:

get_necklace_id :: Necklace -> Maybe Int
get_necklace_id InvalidNecklace = Nothing
get_necklace_id (Necklace x) = Just x

Использование магического возвращаемого значения "-1" является распространенным стилем в языках C-типа с более простыми системами типов. Но обратите внимание, что Maybe Int изоморфен Necklace, поэтому в простейшем случае он мало что добавляет (за исключением доступа к большому количеству общих функций для обработки Maybe, которых может не быть для Necklace). Если вы сделаете Necklace более сложным, то get_necklace_id обретет смысл.

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

ОБНОВЛЕНИЕ: использование fromJust не очень хорошая идея. Чтобы получить «разумные значения по умолчанию» и «отсутствие отказов», вы можете скомпоновать get_necklace_id :: Necklace -> Maybe Int с Data.Maybe.fromMaybe :: a -> Maybe a -> a (одна из распространенных функций обработки Maybe) следующим образом:

from_necklace_id :: Int -> Necklace -> Int
from_necklace_id default = fromMaybe default . get_necklace_id

a_necklace_id :: Necklace -> Int
a_necklace_id = from_necklace_id (-1)

a_necklace_id идентично вашей функции, которая заменяет InvalidNecklace на (-1). Код, которому требуется другое значение по умолчанию, может использовать from_necklace_id.

person Chris Kuklewicz    schedule 25.01.2012
comment
Я переоценю, подходит ли здесь Maybe локально, но изначально я считал это неуместным, потому что в конечном итоге я писал fromJust везде, и, возможно, общие функции выглядели в основном неуместными. Я отредактировал вопрос с другим подходом. - person Jeff Burdges; 25.01.2012
comment
Вы можете очень удобно работать с Maybes без постоянной их распаковки и перепаковки, например. с помощью do-Notation, методов lift..., mfilter, sequence и т. д., поскольку Maybe является монадой и т. д. - person Landei; 25.01.2012
comment
Я делаю различные модификации записей для различных отдельных записей, которые везде требуют разных выражений типа liftM (\x -> x { meow=1 }) v, потому что {meow=1} не создает функцию, как можно было бы ожидать. И do { x <- v, return Just v {meow=1} } дела не улучшает. В конечном итоге я выполняю большое недетерминированное вычисление в монаде списка, а не вычисление одного прохода внутри монады «может быть». catMaybe помогают везде, но простое установление разумных значений по умолчанию помогает больше. - person Jeff Burdges; 26.01.2012
comment
@Jeff: Вы также можете изучить линзы, которые значительно облегчают работу с записями; например, в библиотеке data-lens вы сможете иметь v ^. meow , или fmap (^. meow) v. - person Antal Spector-Zabusky; 26.01.2012
comment
Да, я знаю о fromMaybe, но никогда не находил его убедительным для этого случая. - person Jeff Burdges; 04.02.2012

Почему тип Ожерелье не может представлять только действительные Ожерелья, а Ожерелье Может быть для случаев, когда Ожерелье может быть недопустимым? Или, избегая использования Maybe, что-то вроде (обратите внимание, что я все еще не уверен, какое здесь будет хорошее соглашение об именах):

data Necklace = InvalidNecklace | NecklaceData NecklaceData
data NecklaceData = NecklaceDataRec { necklace_id :: Int, meow :: Int, ... }
person Sgeo    schedule 25.01.2012
comment
По сути, я выполняю вычисление, которое требует разумных значений по умолчанию, но не имеет режимов отказа, вероятно, лучше иметь дело с ними один раз. ‹пожимание плечами› - person Jeff Burdges; 26.01.2012