Это мой парсер
data Parser a = MkParser (String -> Maybe (String, a))
Это анализатор, который анализирует, выполняется ли конкретный предикат.
satisfy :: (Char -> Bool) -> Parser Char
-- takes a predicate (takes a item and gives True or False)
-- Nothing if it's False, parse if it's true
satisfy pred = MkParser sf
where
sf (c:cs) | pred c = Just (cs, c)
sf _ = Nothing
Этот парсер просто дает то, что я хочу, и не меняет входную строку:
pure :: a -> Parser a
pure a = MkParser sf
where
sf inp = Just (inp, a)
Теперь у меня есть этот парсер. Требуется синтаксический анализатор, и ответ передается функции, которая анализирует полученный ответ.
dsc :: Parser a -> (a -> Parser b) -> Parser b
dsc (MkParser pa) pb = MkParser sf
where
sf inp = case pa inp of
Nothing -> Nothing
Just (cs, c) -> unParser (pb c) cs
-- this just runs a parser
unParser :: Parser a -> String -> Maybe (String, a)
unParser (MkParser pa) inp = pa inp
Теперь я пытаюсь проанализировать строку. Если это буква, то цифра, то я хочу дать букву и цифру:
myldV2 :: Parser String
myldV2 = dsc (dsc (satisfy isAlpha) (\x-> satisfy isDigit)) (\y -> pure [x,y])
Но там написано, что x
вне диапазона. Мое решение состояло в том, чтобы воссоздать его вот так, и оно работает (по сути, я сделал это встроенным парсером): Понимание использования скобок в Haskell - Парсер, который зависит от предыдущего парсера, выдает ошибку при использовании скобок
Мое второе решение тоже работает:
lde = dsc p1 f1
where
p1 = satisfy isAlpha
f1 x = dsc p2 f2
where
p2 = satisfy isDigit
f2 y = dsc p3 f3
where
p3 = char '!'
f3 = \_ -> pure [x,y]
Но как мне заставить мое первое решение работать? Этот myldV2 = dsc (dsc (satisfy isAlpha) (\x-> satisfy isDigit)) (\y -> pure [x,y])
По сути, я хочу знать, что я делаю неправильно и как это исправить, чтобы лучше понимать Haskell и что происходит. Спасибо.
Вам нужно будет выполнить вложение внутри функции pb
: dsc (satisfy isAlpha) (\x-> dsc (satisfy isDigit) (\y -> pure [x,y]))
Используйте этот шаблон кода, где для дополнительного удобства применяется инфикс dsc
:
p :: Parser SomeType
p =
parser1 `dsc` \ x1 ->
parser2 `dsc` \ x2 ->
...
parserN `dsc` \ xN ->
pure (someFunction x1 ... xN)
С явными круглыми скобками это звучит так:
p :: Parser SomeType
p =
parser1 `dsc` (\ x1 ->
parser2 `dsc` (\ x2 ->
...
parserN `dsc` (\ xN ->
pure (someFunction x1 ... xN)
)...))
Обратите внимание, как первый \ x1 -> ...
приводит к тому, что область действия x1
достигает самого конца выражения. Другие лямбды аналогичным образом завершают область действия связанной переменной на последней строке кода.
Если хотите, вы можете переписать вышеприведенное, используя dsc
в префиксной позиции. Тем не менее, для монадического оператора, такого как dsc
, как-то идиоматично использовать action `dsc` \ result -> ....
, когда нотацию do
использовать нельзя.
Просто вставьте что-то во второе решение, и вы получите решение, очень похожее на первое. Маленькими шагами вот отправная точка:
lde = dsc p1 f1
where
p1 = satisfy isAlpha
f1 x = dsc p2 f2
where
p2 = satisfy isDigit
f2 y = dsc p3 f3
where
p3 = char '!'
f3 = \_ -> pure [x,y]
Встроенные p3
и f3
:
lde = dsc p1 f1
where
p1 = satisfy isAlpha
f1 x = dsc p2 f2
where
p2 = satisfy isDigit
f2 y = dsc (char '!') (\_ -> pure [x,y])
Встроенные p2
и f2
:
lde = dsc p1 f1
where
p1 = satisfy isAlpha
f1 x = dsc (satisfy isDigit) (\y -> dsc (char '!') (\_ -> pure [x,y]))
Встроенные p1
и f1
:
lde = dsc (satisfy isAlpha) (\x -> dsc (satisfy isDigit) (\y -> dsc (char '!') (\_ -> pure [x,y])))
Если хотите, вы можете сделать часть повторяющейся структуры более заметной с помощью художественных пробелов. Я также поменяю скобки на $
, просто чтобы избежать закрывающих скобок в конце, которые портят расширяемость.
lde = dsc (satisfy isAlpha) $ \x ->
dsc (satisfy isDigit) $ \y ->
dsc (char '!') $ \_ ->
pure [x,y]
Надеюсь, вам сразу бросается в глаза связь со стандартной версией do
-нотации! Ваш dsc
очень похож на (>>=)
, и если бы вы действительно назвали его, вы могли бы написать, что это будет означать то же самое:
lde = do
x <- satisfy isAlpha
y <- satisfy isDigit
char '!' -- OR, if you want to be explicit about it: _ <- char '!'
pure [x,y]
satisfy isAlpha `dsc` \ x -> satisfy isDigit `dsc` \ y -> pure [x,y]
. Или создайтеParser
aMonad
и используйтеdo
-нотацию. Компилятор сообщает вам, что не так, поэтому я не уверен, какую дополнительную информацию вы хотите.