Входные проверки в конструкторах данных Haskell
Как я могу добавить проверки ввода в конструкторы данных Haskell? Скажем, у меня есть
import Data.Time.Calendar
data SchedulePeriod = SchedulePeriod { startDate :: Day
, endDate :: Day
, accrualStart :: Day
, accrualEnd :: Day
, resetDate :: Day
, paymentDate :: Day
, fraction :: Double }
deriving (Show)
и я хочу наложить ограничение startDate < endDate
. Есть ли способ сделать это, не создавая абстрактный тип данных?
Ответы
Ответ 1
Стандартный способ - использовать интеллектуальный конструктор, который проверяет предварительное условие перед созданием значения и не экспортирует используемый им конструктор. Конечно, это создает абстрактный тип данных, как вы сказали.
Единственный способ добиться этого без умного конструктора - это действительно злой хакерство типа системы (и вы не сможете использовать стандартный тип Day
).
Ответ 2
Примите ответ. Я просто пишу это, поэтому я могу объяснить умные деструкторы, о которых я упоминал в комментарии, и я не могу подправить объяснение в комментарии.
Скажем, что у вас есть тип:
data T x y z = A | B x | C y z
ehird уже объяснил, как абстрагироваться от конструктора, который предназначен только для создания "умных" конструкторов. Как вы уже упоминали, это требует скрытия конструкторов, и вы не можете использовать их для сопоставления шаблонов. Однако вы можете решить это с помощью "умного" деструктора, что эквивалентно сопоставлению шаблонов со всеми возможными конструкторами.
Чтобы объяснить это, позвольте сначала начать с того, как мы будем писать функцию типа T, если бы были открыты конструкторы:
myFunction :: T x y z -> d
myFunction t = case t of
A -> f1
B x -> f2 x
C y z -> f3 y z
Из сигнатуры типа функции мы знаем, что типы f1
, f2
и f3
должны быть:
f1 :: d
f2 :: x -> d
f3 :: y -> z -> d
Итак, если бы мы обобщали myFunction
как умный деструктор, мы просто передаем f1
, f2
и f3
в качестве параметров для него:
smartDestructor :: d -> (x -> d) -> (y -> z -> d) -> t -> d
smartDestructor f1 f2 f3 t = case t of
A -> f1
B x -> f2 x
C y z -> f3 y z
Итак, если вы экспортируете smartDestructor
, тогда люди могут в основном сопоставлять шаблоны с вашим типом, не требуя доступа к конструкторам.
Если раньше вы использовали функции maybe
или either
, тогда вы использовали интеллектуальный деструктор, хотя в тех случаях конструкторы не скрыты, поэтому они в основном предоставляются как удобные функции:
maybe :: b -> (a -> b) -> Maybe a -> b
maybe f1 f2 m = case m of
Nothing -> f1
Just a -> f2 x
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f1 f2 e = case e of
Left a -> f1 a
Right b -> f2 b
В вашем случае цель интеллектуального деструктора заключается в том, что вы можете скрыть конструкторы и не выставлять конструкторы.