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

Я пытаюсь разобрать такие даты, как 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 парсеры генеративно?


person user1441998    schedule 12.09.2015    source источник
comment
listUncurry использует монадический do, но для этого нет причин - я хочу исправить и это, и поливариадность :)   -  person user1441998    schedule 12.09.2015
comment
Так что мне действительно чего-то не хватало ... Устранение do может быть таким же простым, как fmap (\[x1,x2,x3] -> h x1 x2 x3) p, как в ответе Дэвида Янга.   -  person duplode    schedule 12.09.2015
comment
@duplode Вы не должны редактировать свои вопросы после того, как нашли ответ. Если есть ответ, который вам нравится, вы должны принять его (щелкните галочку под ним) и оставьте его таким, чтобы его могли увидеть будущие посетители.   -  person AJF    schedule 12.09.2015
comment
ответ показал, что я сосредоточился на тривиальной проблеме в заголовке, но вы можете видеть, что правки изменили только около 1% контента - ни один из трех конкретных вопросов внизу не был рассмотрен. Я проголосовал за него, чтобы поблагодарить его за то, что он помог мне найти и прояснить суть вопроса. :)   -  person user1441998    schedule 12.09.2015
comment
Связано: http://stackoverflow.com/questions/3775446/passing-list-elements-as-parameters-to-curried-function   -  person jub0bs    schedule 26.09.2015


Ответы (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

Поливариадический 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, Functor, Applicative и Monad сделают это довольно приятным).

Сравнение двух подходов

В конечном итоге это зависит от того, какую ошибку это представляет. Если выполнение программы больше не может продолжаться каким-либо значимым образом при возникновении такой ошибки, то первый подход (или что-то подобное) может быть более подходящим, чем второй. Если существует какой-либо способ исправить ошибку, второй подход будет лучше, чем первый.

Другой вопрос, следует ли вообще использовать поливариадную технику. Возможно, было бы лучше реструктурировать программу, чтобы избежать дополнительной сложности поливариадного материала.

person David Young    schedule 12.09.2015
comment
хе, хорошо, честно, я думаю, что меня действительно интересовало, как использовать (<*>), чтобы сделать его поливариадным ... - person user1441998; 12.09.2015
comment
Я изменю q, чтобы переориентировать на это, но дайте мне знать, если вы предпочитаете, чтобы я начал новый вопрос и поручил вам ответить на этот вопрос :) - person user1441998; 12.09.2015
comment
@ user1441998 Обычно предлагается задать новый вопрос, чтобы существующие ответы не были недействительными. Что касается поливариадической проблемы, если вы не используете какие-то уловки с классами типов, которые я бы не рекомендовал для этого, это невозможно (вы не можете написать тип поливариадического listUncurry без уловки с классом типа, не говоря уже о реализации). Я добавил к своему ответу возможный способ использования трюка с классом типов, чтобы показать, как он может работать, и продемонстрировать недостатки этого подхода. - person David Young; 26.09.2015
comment
Если подумать еще раз, поливариадное решение могло работать лучше, чем я думал изначально, в зависимости от обстоятельств проблемы. Это зависит от того, что означает возникновение ошибки в listUncurry (я добавил обсуждение этой проблемы в ответ). - person David Young; 26.09.2015

также я уверен, что мне не следует 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 (:)
person jub0bs    schedule 12.09.2015