Во-первых, общее представление о том, как Haskell оценивает вещи. Считайте это выражение
(\x -> x * x) (2 + 2)
Haskell ленивым; функции не оценивают свои аргументы перед вызовом, поэтому наивный способ сделать это будет:
(2 + 2) * (2 + 2)
Но это удвоит работу!
То, что делается вместо этого, выглядит примерно так:
*
/ \
\ /
(2+2)
То есть среда выполнения запоминает, что 2 + 2
пришла из одного места, и при оценке результат повторно используется в другом части выражения:
*
/ \
\ /
4
, а затем
16
Функция trace
переносит выражение и печатает сообщение только first , когда выражение является «выскочил». Он не распечатывает сообщение снова, если результат запрашивается снова:
ghci> (\x -> x * x) (trace "pop!" (2 + 2))
pop!
16
Следующая часть головоломки - IO
действий. В Haskell подобные утверждению вещи, такие как IO
действия, на самом деле являются значениями, как и все остальное. Они могут быть переданы в качестве аргументов функциям, и они могут быть возвращены из функций. Просто среда выполнения интерпретирует их как описания эффективных действий, выполняемых в реальном мире.
Следовательно, выражения с типом IO something
должны быть оценены (в чистом, ленивом смысле). ) перед тем, как казнить (в смысле «взаимодействует с миром»). Например:
(\x -> x >> x) (trace "test" (putStrLn "foo"))
становится
(>>)
/ \
\ /
(trace "test" (putStrLn "foo"))
здесь мы выскакиваем выражение, и "test" выводится один раз
(>>)
/ \
\ /
putStrLn "foo"
среда выполнения видит что-то вроде
putStrLn "foo", then putStrLn "foo" again
На самом деле то, что вы написали, немного отличается: (trace "test" return) "main is called"
. В вашем коде trace
используется функция return
, а не результирующее значение IO String
. Но эффект аналогичен, функции также имеют значения в Haskell. Выражения, тип которых является функцией, должны быть оценены до ее вызова.
Кроме того, в вашем случае происходит то, что действие main
вычисляется один раз и выполняется несколько раз.
Обратите внимание, что эффекты trace
(печатные материалы на консоли) являются внеполосными с обычным потоком действий IO
. Они случаются по капризам ленивых оценок. Это функция отладки, которая не должна использоваться для «реальной» работы.
Обновление : я забыл ответить на эту часть вопроса: почему trace
имеет поведение, которое вы ожидаете в своих экспериментах с ghci?
trace "test" (return "foo")
- это значение полиморфа c, оно не указывает конкретную монаду (попробуйте :t return "foo"
). правила дефолта ghci создают экземпляр IO
перед его выполнением. Этот полиморфизм заставляет ghci пересчитывать значение каждый раз, когда вы вводите main
. Эффект должен исчезнуть, если вы предоставите явную аннотацию типа, например main = (trace "test" (return "foo")) :: IO String
.