"Зависимые дополнительные" данные в Haskell
Рассмотрим тип DateTime, в котором должна присутствовать дата, но временная часть в секундах является необязательной. Если часть времени есть, может быть и дополнительная часть в миллисекундах. Если миллисекунды присутствуют, может быть и наносекундная часть.
Есть много способов справиться с этим, например:
--rely on smart constructors
data DateTime = DateTime { days:: Int,
sec :: Maybe Int,
ms :: Maybe Int,
ns :: Maybe Int
}
-- list all possibilities
data DateTime = DateOnly Int
| DateWithSec Int Int
| DateWithMilliSec Int Int Int
| DateWithNanoSec Int Int Int Int
-- cascaded Maybe
data DateTime = DateTime Int (Maybe (Int, Maybe (Int, Maybe Int)))
-- cascaded data
data Nano = NoNano | Nano Int
data MilliSec = NoMilliSec | MilliSec Int Nano
data Sec = NoSec | Sec Int MilliSec
data Date = Date Int Sec
Какую конструкцию вы бы использовали (конечно, не ограничиваясь приведенными выше примерами) и почему?
[намерения]
Я изучаю возможности типа даты в Frege (http://code.google.com/p/frege/), используя date4j DateTime
в качестве ориентирной линии ( поскольку Haskell дата и время lib слишком сложны, а java.util.Date
слишком сломан). В моей текущей реализации игрушек все поля обязательны, но, конечно, было бы неплохо освободить пользователя от нежелательной точности (а исходная реализация имеет необязательные поля).
Итак, основные цели:
- безопасность: нелегальные государства следует избегать любой ценой.
- удобство: должно быть легко работать с типом, например. сопоставление шаблонов было бы круто, календарные вычисления должны быть легкими...
Не так важно:
- производительность: Конечно, работа с типом не должна быть слишком медленной, но для типичного использования ей не нужно зацикливать последний такт
- memory: в случаях, когда это действительно имеет значение, было бы легко получить более компактный формат хранения
- краткая реализация: это библиотека, и я готов добавить весь код, необходимый для того, чтобы сделать вещи гладкими.
Тем не менее, все это очень условно и не следует воспринимать слишком серьезно.
Ответы
Ответ 1
(Это не ответ, но он слишком длинный для комментария и будет более понятным здесь.)
Существует еще один способ, которым вы могли бы справиться с этим: иметь один тип DateTime
, который сохраняет все поля всегда вместе с параметром, представляющим точность, например
data Precision = Days | Seconds | Milliseconds | Nanoseconds deriving (Ord, Eq {- etc -})
data DateTime = DateTime { prec :: Precision,
days :: Int,
sec :: Int,
ms :: Int,
ns :: Int }
И используйте интеллектуальные конструкторы, которые устанавливают неиспользуемые параметры в 0
. Если у вас есть dateDifference
или что-то еще, вы можете распространять точность (экземпляр Ord
сделал бы это аккуратным).
(У меня мало информации о том, насколько хорош /Haskell -y, но другие решения кажутся довольно запутанными, может быть, это более элегантно.)
Ответ 2
"Незаконные состояния следует избегать любой ценой" и "сопоставление образцов было бы круто" - прекрасные принципы, которые в этом случае находятся в прямом конфликте друг с другом.
Кроме того, даты и время являются gnarly человеческими культурными конструкциями с большим количеством краевых случаев и неправильных углов. Они не такие правила, которые мы можем легко кодировать в системе типов.
Итак, в этом случае я бы пошел с непрозрачным типом данных, интеллектуальными конструкторами и интеллектуальными деконструкторами. Всегда всегда просматривайте шаблоны и шаблоны для случаев, когда мы хотим использовать сопоставление образцов.
(И я даже не обсуждал зависимые факультативные данные как мотивирующий фактор.)
Ответ 3
Вдохновленный решением @dbaupp, я хотел бы добавить версию типа phantom для кандидатов:
-- using EmptyDataDecls
data DayPrec
data SecPrec
data MilliPrec
data NanoPrec
data DateTime a = DateTime { days :: Int, sec :: Int, ms :: Int, ns :: Int }
date :: Int -> DateTime DayPrec
date d = DateTime d 0 0 0
secDate :: Int -> Int -> DateTime SecPrec
secDate d s = DateTime d s 0 0
...
--will work only for same precision which is a Good Thing (tm)
instance Eq (DateTime a) where
(DateTime d s ms ns) == (DateTime d' s' ms' ns') = [d,s,ms,ns] == [d',s',ms',ns']
Если я не ошибаюсь, это позволяет мне работать с одним типом, но для того, чтобы отличать уточнения, если нужно. Но я думаю, что будет и какой-то недостаток...
Ответ 4
Предполагая, что вы хотите решить эту проблему (в отличие от более общей), библиотека decimal может быть тем, что вы хотеть.