Подтвердить что ты не робот

"Зависимые дополнительные" данные в 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: в случаях, когда это действительно имеет значение, было бы легко получить более компактный формат хранения
  • краткая реализация: это библиотека, и я готов добавить весь код, необходимый для того, чтобы сделать вещи гладкими.

Тем не менее, все это очень условно и не следует воспринимать слишком серьезно.

4b9b3361

Ответ 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 может быть тем, что вы хотеть.