Как использовать контроль.Монада.Государство с парсеком?
Я удивлен, что не смог найти никакой информации об этом. Должно быть, я единственный человек, у которого есть какие-то проблемы с этим.
Итак, допустим, у меня есть счетчик тире. Я хочу, чтобы он посчитал количество тире в строке и вернул строку. Представьте, что я привел пример, который не будет работать с использованием обработки состояния 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 ответа:
На самом деле у вас здесь происходит множество проблем, все из которых относительно неочевидны в первый раз.
Начиная с самого простого:
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)