Как я могу превратить этот парсер в аппликативный, поливариадный?

Я пытаюсь разобрать даты, такие как 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 (:)
Другие вопросы по тегам