Понимание использования скобок в Haskell. Парсер, который зависит от предыдущего парсера, выдает ошибку при использовании скобок

Хорошо, я пытаюсь изучить 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?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем первом примере:

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.

Другие вопросы по теме