Хорошо, я пытаюсь изучить Haskell.
Это мой парсер.
import Data.Char
data Parser a = MkParser (String -> Maybe (String, a))
Это анализатор, который анализирует строку один раз и в зависимости от того, что он анализирует, запускает другой анализатор.
-- second parse depends on the first
(<<<=) :: Parser a -> (a -> Parser b) -> Parser b
-- a parse, gives Nothing | Just (string, ans depending on a)
(MkParser pa) <<<= k = MkParser sf
where
sf inp = case pa inp of
Nothing -> Nothing
Just (cs, c) -> unParser (k c) cs
unParser :: Parser a -> String -> Maybe (String, a)
-- applies a parser manually
unParser (MkParser pa) inp = pa inp
Теперь я использую его здесь (isDigit
просто проверяет, является ли символ цифрой, а isAlpha
проверяет, является ли это буквой). Я также использую этот базовый парсер (анализирует, является ли персонаж тем, что я ищу)
char :: Char -> Parser Char
char wanted = MkParser sf
where
sf (c:cs) | c == wanted = Just (cs, c)
sf _ = Nothing
Теперь, когда я это делаю:
t = satisfy isAlpha <<<= \x -> satisfy isDigit <<<= \y -> char x
оно работает!
Но в тот момент, когда я заключаю это в скобки
t = satisfy isAlpha <<<= (\x -> satisfy isDigit) <<<= \y -> char x
выдает ошибку, говоря, что переменная x
не входит в область видимости... Почему?
Сначала я просто расставил скобки, потому что так легче читать. Но оказывается, что мой код работает только тогда, когда я удаляю скобки.
Я попробовал поработать с ним и понял, что это тоже работает:
t = satisfy isAlpha <<<= (\x -> satisfy isDigit <<<= \y -> char x)
Вопрос 1) Но почему? Если он это сделает (\x -> satisfy isDigit <<<= \y -> char x)
сначала, он не будет знать, что такое x
, так как же это работает?
Вопрос 2) Каков порядок оценки, если я не использую скобки?
Вопрос 3) А почему у меня не работает (\x -> satisfy isDigit)
?
Все вопросы связаны, и я уверен, что просто неправильно понимаю порядок оценки. Раньше я программировал на языках ООП и чувствую, что неправильно понимаю скобки и порядок вычислений в Haskell?
В вашем первом примере:
t = satisfy isAlpha <<<= (\x -> satisfy isDigit) <<<= \y -> char x
Похоже, вы думаете и о левой, и о правой частях оператора (<<<=)
как о действиях синтаксического анализа.
Но на самом деле левая часть — это действие синтаксического анализа, а правая — функция, которая принимает результат предыдущего синтаксического анализа и возвращает другое действие синтаксического анализа.
t = parser1 <<<= parser2 <<<= parser3
where
parser1 = satisfy isAlpha
parser2 x = satisfy isDigit
parser3 y = char x -- x isn’t in scope
В вашем втором примере:
t = satisfy isAlpha <<<= (\x -> satisfy isDigit <<<= \y -> char x)
Это устраняет проблему с областью действия, поскольку поясняет, что третий парсер должен находиться в области видимости x
, другими словами, он должен иметь x
в своем замыкании.
t = parser1 <<<= function1
where
parser1 = satisfy isAlpha
function1 x = parser2 <<<= function3
where
parser2 = satisfy isDigit
function3 y = char x
Когда вы пишете лямбду без круглых скобок, ее тело представляет собой оставшуюся часть выражения, простирающуюся как можно дальше вправо. Таким образом, эти две формы в точности эквивалентны:
satisfy isAlpha <<<= (\x -> satisfy isDigit <<<= \y -> char x)
satisfy isAlpha <<<= \x -> satisfy isDigit <<<= \y -> char x
Круглые скобки не влияют на порядок вычислений, а только на группировку выражений. Однако не будет проблем с первым вычислением лямбды \x -> …
— она просто вычисляет функцию, которая позже будет применена к аргументу.
Фактический порядок вычислений в Haskell определяется сопоставлением с образцом из-за лени. В языках, которые быстро оцениваются, вы «вталкиваете» входные данные в функции, и они немедленно вызываются, поэтому стек вычислений связан с выражениями вызова f(x)
. При ленивом вычислении функции вызываются только тогда, когда вы «извлекаете» из них выходные данные путем сопоставления с образцом, поэтому вместо этого кадры стека получаются из выражений case
.