Почему функция «ошибка» много раз печатает Exception

Мне было интересно, почему функция ошибки возвращает это в ghci:

Сначала одним звонком:

Prelude> error ""
*** Exception: 
CallStack (from HasCallStack):
  error, called at <interactive>:3:1 in interactive:Ghci1

Потом с двумя:

Prelude> error (error "")
*** Exception: *** Exception: 
CallStack (from HasCallStack):
  error, called at <interactive>:2:8 in interactive:Ghci1

А с тремя и так далее:

Prelude> error (error ( error ""))
*** Exception: *** Exception: *** Exception: 
CallStack (from HasCallStack):
  error, called at <interactive>:1:16 in interactive:Ghci1

Почему многие «*** Exception:» печатаются как ошибки?

Я сделал эту функцию, которая также получает число и печатает исключение:

printException n = foldr (\c r -> c error r) id (replicate n (.)) ""

Но мне также было интересно, почему

until (const False) error ""

Не печатает Exception бесконечно.

Возможно, вам также будет интересно прочитать ответы на один из моих вопросов, заданных несколько лет назад.

Daniel Wagner 21.04.2023 16:35
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
86
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Пусть e будет любым выражением типа String. Затем error e вызывает исключение, которое (в нормальных условиях) печатает *** Exception:, за которым следует строка e.

Следовательно, e оценивается сейчас (а не раньше!). Это может включать в себя оценку другого error e', который напечатает еще один *** Exception:, за которым следует e'.

И так далее.

Ключевым моментом здесь является то, что e оценивается лениво, только когда оно печатается, поэтому мы наблюдаем *** Exception: перед ним.

Что касается последнего вопроса, обратите внимание, что until (const False) f x никогда не будет оценивать f (f (f ... (f x)). Это потому, что по лени const False x, const False (f x), const False (f (f x)), ... не нужно оценивать x, ни f x, ни f (f x),...

Следовательно, в вашем примере error никогда не называется.

У вас ошибка в сообщении об ошибке. Из-за лени, когда он печатает внешнюю ошибку, он вызывает внутреннюю ошибку, которая напечатает вторую.

Таким образом, он начнет печатать сообщение:

*** Exception: …

но затем он начинает putStrLn сообщения. Обратите внимание, что это сообщение еще не было оценено. Действительно, Haskell ленив, поэтому он будет оценивать сообщение только в том случае, если он собирается его напечатать, поэтому теперь возникает вторая ошибка, и поэтому он начинает печатать второе сообщение.

Но мне также было интересно, почему

until (const False) error ""

Не печатает исключение бесконечно.

Здесь он начинает печатать строку, но никогда ничего не генерирует. Действительно, он создаст выражение, похожее на error (error (error (… (error "") …))), но никогда не будет вычислять ни одно error, даже внешнее, поскольку постоянно занят построением гигантского дерева выражений.

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

error ленив, и String также оценивается лениво. Кроме того, поскольку error :: String -> a, результат error может быть входом функции error. И строки также печатаются лениво, символ за символом.

Может быть, чтобы лучше понять ситуацию, вы можете подумать, например, о такой функции:

errorsFrom n = error (show n ++ "\n" ++ errorsFrom (n+1))

Затем оцените на консоли errorsFrom 0, и вы увидите это:

*** Exception: 0
*** Exception: 1
*** Exception: 2
*** Exception: 3
*** Exception: 4
*** Exception: 5
*** Exception: 6
*** Exception: 7
*** Exception: 8
*** Exception: 9
*** Exception: 10
...

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