У меня есть такой код:
-- my parser, takes a function that takes a string and gives
-- the suffix and the answer
data Parser a = MkParser (String -> Maybe (String, a))
unParser :: Parser a -> String -> Maybe (String, a)
unParser (MkParser sf1) = sf1
-- gives `a` as the answer, never fails, doesn't change input string
pure :: a -> Parser a
pure a = MkParser (\inp -> Just (inp, a))
-- Choice... if pa fails, try pa2
(<|>) :: Parser a -> Parser a -> Parser a
MkParser sf1 <|> p2 = MkParser g
where
g inp = case sf1 inp of
Nothing -> unParser p2 inp
j -> j -- the Just case
-- | 0 or more times, collect the answers into a list.
many :: Parser a -> Parser [a]
many p = some p <|> pure []
-- Explanation: To repeat 0 or more times, first try 1 or more
-- times! If that fails, then we know it's 0 times, and the answer is the
-- empty list.
-- | 1 or more times, collect the answers into a list.
some :: Parser a -> Parser [a]
some p = liftA2 (:) p (many p)
-- Explanation: To repeat 1 or more times, do 1 time, then 0 or more times!
liftA2 :: (a -> b -> c) -> Parser a -> Parser b -> Parser c
liftA2 op (MkParser sf1) p2 = MkParser g
where
g inp = case sf1 inp of
Nothing -> Nothing
Just (middle, a) ->
case unParser p2 middle of
Nothing -> Nothing
Just (rest, b) -> Just (rest, op a b)
и я тестирую код с помощью этого базового парсера, который просто дает успех, если это то char
, которое я ищу:
char :: Char -> Parser Char
char wanted = MkParser sf
where
sf (c:cs) | c == wanted = Just (cs, c)
sf _ = Nothing
Для запуска парсеров я использую это
runParser :: Parser a -> String -> Maybe a
runParser (MkParser sf) inp = case sf inp of
Nothing -> Nothing
Just (_, a) -> Just a
Теперь я делаю runParser (many (char '!')) "testing"
и получаю Just ""
.
Я пытаюсь пройти через код. Сначала (many (char '!'))
звонит some (char '!')
.
Это вызывает liftA2 (:) p (many p)
. И вот many p
звонит liftA2 (:) p (many p)
снова. Это повторяется бесконечно, верно? Я новичок в Haskell, но предполагаю, что из-за ленивых вычислений он в конечном итоге пытается вычислить p
?. runParser (char '!') "testing"
дает Nothing
, следовательно, первый liftA2 (:) p (many p)
тоже дает Nothing
, верно? Это переходит ко второму случаю some p <|> pure []
и pure []
дает Just []
. Поэтому ответ должен быть Just []
.
Но ответ, который я получаю, когда бегу runParser (many (char '!')) "Testing"
, — Just ""
. Откуда взялся ""
?
Связанный: Правильны ли эти шаги?
many (char '!') = some (char '!') <|> pure []
-- calls some p
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- here, it ways for the third argument of liftA2, or does it
-- start trying to run liftA2 with the second argument?
-- (many (char '!')) calls this:
many (char '!') = some (char '!') <|> pure []
-- which then again calls
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- so now it looks like this:
liftA2 (:) (char '!') ( liftA2 (:) (char '!') (many (char '!')) )
-- When does it start evaluating?
-- Because `(many (char '!')` can't technically run because
-- it needs to wait for the third argument of
liftA2 (:) (char '!') (many (char '!'))
-- which never gives an answer since it keeps recursing right?
Если бы мне пришлось гадать:
many (char '!') = some (char '!') <|> pure []
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- run `liftA2 (:) (char '!')`, get `Nothing`
-- go to the second choice of <|>
<|> pure []
-- give Just []
Just []
Это правильно?
Еще 1 изменение: (Извините, мне следовало добавить эти вопросы в исходное сообщение, но, поскольку все они связаны, я не хочу создавать еще один вопрос)
Если я сделаю
runParser (many (char '!')) "!esting"
Шаги:
(many (char '!'))
many p = some p <|> pure []
-- calls `some p`
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- the first `char !` (second argument) runs, and it is a success.
-- the success gives Just ("esting", '!') to the third
-- argument of liftA2.
-- Now it goes to the third argument of liftA2, which is
(many (char '!')
-- which calls
many p = some p <|> pure [] -- but the string is "esting"
-- this calls
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- the string is "esting"
-- this fails, so it tries the second choice of
some p <|> pure []
-- which is `pure []` which just gives []
-- Now we go back to the first
liftA2 (:) (char '!') (many (char '!'))
-- where the first answer is '!' and the second answer is '[]'
-- and it combines it with `:`. So we have
-- Just ("sting", '!' : '[]')
Обратите внимание, что ""
— это то же самое, что []
.
В Haskell пустой список типа [a]
печатается как []
, за исключением случаев, когда a = Char
. Для [Char]
, также известного как String
, он печатается как ""
.
Непустые списки Char
также имеют специальное обозначение: вместо ['H', 'e', 'l', 'l', 'o']
мы получаем "Hello"
в качестве вывода. Тем не менее, эти два значения совершенно одинаковы.
Что касается шагов вычислений, порядок вычислений в Haskell сложен, поскольку при вызове f x y z
именно f
решает, что оценивать среди своих аргументов. Сначала он мог оценить x
, а затем, в зависимости от результата, оценить y
или z
. Или вместо этого он может начать с оценки z
...
В любом случае, к счастью, порядок не имеет большого значения. Пока вы расширяете определения, как вы это сделали, и пока вы не застреваете в бесконечном цикле (или другом нижнем значении), ваши шаги семантически верны. В худшем случае вы оцените больше, чем нужно, но конечный результат всегда один и тот же.
many (char '!') = some (char '!') <|> pure []
-- calls some p
Правильно, поскольку <|>
решает сначала попробовать первый парсер.
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- here, it ways for the third argument of liftA2, or does it
-- start trying to run liftA2 with the second argument?
Грубо говоря, liftA2
начинается со второго аргумента. В зависимости от этого третий может быть оценен позже.
-- (many (char '!')) calls this:
many (char '!') = some (char '!') <|> pure []
-- which then again calls
some (char '!') = liftA2 (:) (char '!') (many (char '!'))
-- so now it looks like this:
liftA2 (:) (char '!') ( liftA2 (:) (char '!') (many (char '!')) )
-- When does it start evaluating?
Это правильно, но мы никогда до этого не доходим. char '!'
в вашей входной строке завершается с ошибкой Nothing
, и, видя это, liftA2
останавливает вычисление с результатом Nothing
.
Теперь мы попробуем вторую сторону .. <|> pure []
, отсюда и финальную Just []
, или, скорее, Just ""
в том виде, в каком она на самом деле напечатана.
(Обратите внимание, что это выполнение происходит с вашей входной строкой «Тестирование». Конечно, в других строках мы можем получить другое поведение.)
@user20102550 user20102550 Добавил более подробную информацию об этом.
Спасибо. Да, я только что понял, что лучше попробовать другую входную строку, которая действительно дает успех, чтобы посмотреть, пойму ли я шаги лучше. Я отредактировал свое сообщение и попытался просмотреть код, если входная строка «!esting». Если хотите, можете еще раз проверить, правильно ли мое решение! Я приму ваш ответ несмотря ни на что, поскольку он отвечает на исходный вопрос. Спасибо!
@user20102550 user20102550 Это выглядит на 99% правильно. Если говорить более педантично, третий аргумент LiftA2 не получает Just ("esting", '!')
, а только часть "esting"
. Это связано с тем, чтоliftA2 передает только неиспользованную строку. '!' вместо этого используется позже для формирования окончательного результата, как вы это сделали. Однако окончательный результат Just ("esting", '!' : [])
, а не Just ("sting", '!' : '[]')
.
Спасибо. Я только что отредактировал свой пост, чтобы попытаться прояснить фактические шаги. Я не понимаю, в какой момент Haskell начинает оценивать
liftA2
. Потому чтоliftA2
работает только со вторым парсером, но второй парсер никогда не передается как есть(many (char '!'))
, который продолжает вызыватьliftA2
снова? Правильно ли мое решение?