Недавно я определил экземпляр Applicative для нового типа поверх (,,,)
, «четырехугольника». (Стандартная библиотека определяет экземпляр для (,)
, но не (,,,)
. Это нормально, так как стандартная реализация имеет семантику, отличную от той, что была после.)
Фон есть; Я анализирую некоторые старые данные, и формат даты в данных неоднозначен. Каждую дату в данных можно разбить на четыре варианта, хранящихся в квадраторе. Затем я хочу проверить каждую дату в квадрате, чтобы исключить семантически неверные даты. (Нет месяцев с 32 днями, нет месяца 34, нет 5-го квартала и т. Д.) Наконец, я хочу взять каждую дату в наборе данных и сократить весь набор до четырех, представляющих, какие форматы дат действительны для всего набора. Затем я выбираю лучший формат из этих вариантов и предполагаю, что это формат даты набора данных.
Вся эта операция очень легко выразить как аппликативные операции над структурой квадрата.
Вот основная форма кода:
Мой новый тип:
newtype DQ a = DQ (a, a, a, a) -- date quad
deriving ...
instance Functor DQ where
g `fmap` f = pure g <*> f
instance Applicative DQ where
pure x = DQ (x, x, x, x)
DQ (g, h, i, j) <*> DQ (a, b, c, d) = DQ (g a, h b, i c, j d)
Некоторые обязательные «чистые» функции:
parseDateInt :: Int -> DQ Date
validateDate :: Date -> Bool
extractBestDate :: DQ Date -> DQ Bool -> Date
Так что, как только у нас будет четверка проанализированных дат (от parseDateInt
), нам нужно проверить их:
validateDates :: DQ Date -> DQ Bool
validateDates = (validateDate <$>)
(Пока это только Functor, но вы также можете написать
(pure validateDate <*>)
.
Стоит также отметить симметрию между проверкой одного
элемент, и проверка каждого элемента набора - чтобы проверить один, вы могли бы написать
validateDate $ date
; чтобы проверить набор, вы пишете
validateDate <$> dates
. Вот почему fmap
записывается как <$>
, это приложение функции к функтору.)
Шаг после этого состоит в том, чтобы взять набор допустимых разборов и сложить
в окончательный результат:
intuitDateType :: [DQ Bool] -> DQ Bool
intuitDateType dates = foldl1 (liftA2 (&&)) dates
Так что теперь вы можете перейти от [Int]
в файле данных к DQ Bool
представляющие, возможно, допустимые представления даты для набора данных.
(И оттуда, связать каждую точку данных с объектом реальной даты,
вместо шелушащегося Int, который был поставлен.)
Так или иначе, этот пост стал немного длиннее, но идея в том, что
Аппликативный пример позволил мне решить мою проблему примерно в 3 строки
кода. Моя проблемная область неоднократно применяла функции к данным в
контейнер, который делает аппликативный функтор. Для этих данных нет операции join
, поэтому экземпляр Monad не имеет особого смысла.