Как я могу превратить этот парсер в аппликативный, поливариадный?
Я пытаюсь разобрать даты, такие как 09/10/2015 17:20:52
:
{-# LANGUAGE FlexibleContexts #-}
import Text.Parsec
import Text.Parsec.String
import Text.Read
import Control.Applicative hiding (many, (<|>))
data Day = Day
{ mo :: Int
, dy :: Int
, yr :: Int
} deriving (Show)
data Time = Time
{ hr :: Int
, min :: Int
, sec :: Int
} deriving (Show)
day = listUncurry Day <$> (sepCount 3 (char '/') $ read <$> many digit)
time = listUncurry Time <$> (sepCount 3 (char ':') $ dign 2 )
dign :: (Stream s m Char, Read b) => Int -> ParsecT s u m b
dign = (read <$>) . flip count digit
-- how generalize to n?
listUncurry h [x1,x2,x3] = h x1 x2 x3
sepCount n sep p = (:) <$> p <*> (count (n-1) $ sep *> p)
У меня есть предчувствие, что какая-то zipWithN
будет обобщать listUncurry
, Может быть какой-то foldl ($)
?
Как побочный вопрос (из любопытства), может parsec
парсеры будут использоваться генеративно?
2 ответа
На самом деле, вам нужно только Functor
:
listUncurry :: Functor f => (a -> a -> a -> r) -> f [a] -> f r
listUncurry h p =
(\[x, y, z] -> h x y z) <$> p
Мне подсказка, что только Functor
это необходимо, когда у вас есть код шаблона:
do x <- m
return (f ...)
Это эквивалентно
m >>= (\x -> return (f ...))
который так же, как
fmap (\x -> f ...) m
Это потому, что законы монады подразумевают эту идентичность:
fmap f xs = xs >>= return . f
Polyvariadic listUncurry
Я действительно не рекомендую это в большинстве случаев, так как это превращает ошибки времени компиляции в ошибки времени выполнения, но именно так вы можете реализовать поливариадное listUncurry
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
class ListUncurry a x r where
listUncurry :: a -> [x] -> r
instance ListUncurry k a r => ListUncurry (a -> k) a r where
listUncurry f (x:xs) = listUncurry (f x) xs
listUncurry _ _ = error "listUncurry: Too few arguments given"
instance ListUncurry r a r where
listUncurry r [] = r
listUncurry _ _ = error "listUncurry: Too many arguments given"
Вам понадобится много явных аннотаций типов, если вы тоже будете их использовать. Вероятно, есть способ использовать семейство типов или функциональную зависимость, чтобы помочь с этим, но я не могу думать об этом в самый раз. Поскольку это, вероятно, решаемо (по крайней мере, в некоторой степени), на мой взгляд, большая проблема заключается в том, что ошибки типа изменяются с ошибок времени компиляции на ошибки времени выполнения.
Пример использования:
ghci> listUncurry ord ['a'] :: Int
97
ghci> listUncurry ((==) :: Int -> Int -> Bool) [1,5::Int] :: Bool
False
ghci> listUncurry ((==) :: Char -> Char -> Bool) ['a'] :: Bool
*** Exception: listUncurry: Too few arguments given
ghci> listUncurry ((==) :: Char -> Char -> Bool) ['a','b','c'] :: Bool
*** Exception: listUncurry: Too many arguments given
Безопаснее listUncurry
Если вы измените класс на
class ListUncurry a x r where
listUncurry :: a -> [x] -> Maybe r
и соответственно измените случаи ошибок в экземплярах, вы по крайней мере получите лучший интерфейс для обработки ошибок. Вы также можете заменить Maybe
с типом, который различает ошибки аргумента "слишком много" и "слишком мало", если вы хотите сохранить эту информацию.
Я чувствую, что это было бы немного лучше подхода, хотя вам нужно будет добавить немного больше обработки ошибок (Maybe
"s Functor
, Applicative
а также Monad
интерфейсы сделают это довольно хорошим, хотя).
Сравнивая два подхода
В конечном итоге это зависит от того, какую ошибку это будет представлять. Если выполнение программы больше не может продолжаться каким-либо значимым образом, если он сталкивается с такой ошибкой, то первый подход (или что-то подобное) может быть более подходящим, чем второй. Если есть какой-то способ исправить ошибку, второй подход будет лучше, чем первый.
Должен ли поливариадный метод использоваться в первую очередь - это другой вопрос. Возможно, было бы лучше реструктурировать программу, чтобы избежать дополнительной сложности поливариадных вещей.
также я уверен, что не должен быть
snoc
составление списка - как правильно это сделать?
Следующая реализация sepCount
более эффективно:
-- | @sepCount n sep p@ applies @n@ (>=1) occurrences of @p@,
-- separated by @sep@. Returns a list of the values returned by @p@.
sepCount n sep p = p <:> count (n - 1) (sep *> p)
where (<:>) = liftA2 (:)