Как использовать контроль.Монада.Государство с парсеком?


Я удивлен, что не смог найти никакой информации об этом. Должно быть, я единственный человек, у которого есть какие-то проблемы с этим.

Итак, допустим, у меня есть счетчик тире. Я хочу, чтобы он посчитал количество тире в строке и вернул строку. Представьте, что я привел пример, который не будет работать с использованием обработки состояния parsec. Так что это должно сработать:
dashCounter = do
  str <- many1 dash
  count <- get
  return (count,str)


dash = do
  char '-'
  modify (+1)

И действительно, это компилирует. Хорошо, поэтому я пытаюсь использовать его:

:t parse dashCounter "" "----"
parse dashCounter "" "----"
  :: (Control.Monad.State.Class.MonadState
        t Data.Functor.Identity.Identity,
      Num t) =>
     Either ParseError (t, [Char])

Ладно, в этом есть смысл. Он должен вернуть состояние и строку. Круто.

>parse dashCounter "" "----"

<interactive>:1:7:
    No instance for (Control.Monad.State.Class.MonadState
                       t0 Data.Functor.Identity.Identity)
      arising from a use of `dashCounter'
    Possible fix:
      add an instance declaration for
      (Control.Monad.State.Class.MonadState
         t0 Data.Functor.Identity.Identity)
    In the first argument of `parse', namely `dashCounter'
    In the expression: parse dashCounter "" "----"
    In an equation for `it': it = parse dashCounter "" "----"

Упс. Но тогда как он вообще мог надеяться на успех? Нет никакого способа ввести начальное состояние.

Существует также функция:

>runPT dashCounter (0::Int) "" "----"
Но это дает аналогичную ошибку.
<interactive>:1:7:
    No instance for (Control.Monad.State.Class.MonadState Int m0)
      arising from a use of `dashCounter'
    Possible fix:
      add an instance declaration for
      (Control.Monad.State.Class.MonadState Int m0)
    In the first argument of `runPT', namely `dashCounter'
    In the expression: runPT dashCounter (0 :: Int) "" "----"
    In an equation for `it':
        it = runPT dashCounter (0 :: Int) "" "----"
Я чувствую, что должен запустить state на нем, или должна быть функция, которая уже делает это внутренне, но я не могу понять, куда идти отсюда.

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

Тем не менее, Мистер Маккэн выяснил, как это должно сочетаться, и окончательный код будет выглядеть следующим образом:
dashCounter = do
  str <- many1 dash
  count <- get
  return (count,str)

dash = do
  c <- char '-'
  modify (+1)
  return c

test = runState (runPT dashCounter () "" "----------") 0

Большое спасибо.

3 8

3 ответа:

На самом деле у вас здесь происходит множество проблем, все из которых относительно неочевидны в первый раз.

Начиная с самого простого: dash возвращает (), что, похоже, не то, что вы хотите, учитывая, что вы собираете результаты. Вы, вероятно, хотели что-то вроде dash = char '-' <* modify (+1). (Обратите внимание, что я использую оператор из Control.Applicative здесь, потому что он выглядит более аккуратным)

Далее, прояснение вопроса путаницы: когда вы получаете разумно выглядящую подпись типа в GHCi, обратите внимание на контекст (Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t). Это не говорит о том, что вещи являются , это говорит, что вы хотите, чтобы они должны быть. Ничто не гарантирует, что экземпляры, о которых он просит, существуют, и на самом деле они не существуют. Identity не является монадой государства!

С другой стороны, вы абсолютно правы, думая, что parse не имеет смысла; вы не можете использовать его здесь. Рассмотрим его тип: Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a. Как это обычно бывает с трансформаторами монад, Parsec является синонимом ParsecT, применяемым к тождеству монада. И хотя ParsecT предоставляет пользовательское состояние, вы, по-видимому, не хотите его использовать, а ParsecT не дает экземпляр MonadState в любом случае. Вот единственный релевантный пример: MonadState s m => MonadState s (ParsecT s' u m). Другими словами, чтобы рассматривать синтаксический анализатор как монаду состояния, вы должны применить ParsecT к какой-то другой монаде состояния. Это подводит нас к следующей проблеме: неоднозначности. Вы используете много методов класса типа и никаких сигнатур типа, поэтому вы, вероятно, столкнетесь с ситуациями, когда GHC не может знать, что именно тип вы действительно хотите, так что вы должны сказать это. Теперь, в качестве быстрого решения, давайте сначала определим синоним типа, чтобы дать имя стеку трансформаторов монад, который мы хотим:
type StateParse a = ParsecT String () (StateT Int Identity) a

Дайте dashCounter соответствующую подпись типа:

dashCounter :: StateParse (Int, String)
dashCounter = do str <- many1 dash
                 count <- get
                 return (count,str)

И добавить специальную функцию "run":

runStateParse p sn inp count = runIdentity $ runStateT (runPT p () sn inp) count

Теперь, в GHCi:

Main> runStateParse dashCounter "" "---" 0
(Right (3,"---"),3)
Кроме того, обратите внимание, что довольно часто используется newtype вокруг стека трансформаторов вместо просто синонима типа. Это может помочь с неоднозначностью проблемы в некоторых случаях, и, очевидно, избегает в конечном итоге с гигантскими сигнатурами типа.

Если вы хотите использовать компонент пользовательского состояния Parsec в качестве встроенной функции, то вы можете использовать монадические функции getState и modifyState.

Я старался оставаться верным вашему примеру программы, хотя использование возврата dash не кажется полезным.

import Text.Parsec

dashCounter :: Parsec String Int (Int, [()])
dashCounter = do
  str <- many1 dash
  count <- getState
  return (count,str)

dash :: Parsec String Int ()
dash = do
  char '-'
  modifyState (+1)

test = runP dashCounter 0 "" "---"

Обратите внимание, что runP действительно решает вашу проблему runState.

В то время как эти ответы решают эту конкретную проблему, они игнорируют более серьезную основную проблему с таким подходом. Я хотел бы описать его здесь для всех, кто смотрит на этот ответ.

Существует разница между пользовательским состоянием и использованием трансформатора StateT. Внутреннее состояние пользователя сбрасывается на откат, но Государственного нет. Рассмотрим следующий код. Мы хотим добавить один к нашему счетчику, если есть тире и два, если есть плюс. Они производят различное результаты.

Как можно видеть, как использование внутреннего состояния, так и подключение трансформатора состояния обеспечивают правильный результат. Последнее происходит за счет необходимости явно поднимать операции и быть гораздо более осторожным с типами.

import Text.Parsec hiding (State)
import Control.Monad.State
import Control.Monad.Identity

f :: ParsecT String Int Identity Int
f = do
  try dash <|> plus
  getState

dash = do
  modifyState (+1)
  char '-'
plus = do
  modifyState (+2)
  char '+'

f' :: ParsecT String () (State Int) ()
f' = void (try dash' <|> plus')

dash' = do
  modify (+1)
  char '-'

plus' = do
  modify (+2)
  char '+'

f'' :: StateT Int (Parsec String ()) ()
f'' = void (dash'' <|> plus'')

dash'' :: StateT Int (Parsec String ()) Char
dash'' = do
  modify (+1)
  lift $ char '-'

plus'' :: StateT Int (Parsec String ()) Char
plus'' = do
  modify (+2)
  lift $ char '+'

Это результат выполнения f, f' и f".

*Main> runParser f 0 "" "+"
Right 2
*Main> flip runState 0 $ runPT f' () "" "+"
(Right (),3)
*Main> runParser (runStateT f'' 0) () "" "+"
Right ((),2)