Функция trace в ghci не печатает при втором вызове - PullRequest
2 голосов
/ 10 апреля 2020

Проблема:

Функция трассировки не работает при втором вызове, но это происходит, только если функция, содержащая трассировку, загружена в ghci.

Вопрос

  1. Почему это происходит?
  2. Как я могу загрузить модули Haskel и при этом иметь ожидаемое поведение?

Неожиданное поведение:

У меня есть файл с именем test.hs

import Debug.Trace (trace)
main = trace "test" return "main is called"

Затем я набираю следующее в ghci. Примечание о втором вызове main trace не напечатало «test»

Prelude> :l test
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, one module loaded.
*Main> main
test
"main is called"
*Main> main      -- No output from trace?
"main is called"

Ожидаемое поведение

Однако, если я введу основную функцию в ghci, я получу ожидаемое Поведение

Prelude> import Debug.Trace (trace)
Prelude Debug.Trace> main = trace "test" return "main is called" 
Prelude Debug.Trace> main
test
"main is called"
Prelude Debug.Trace> main -- Output from trace
test
"main is called"
Prelude Debug.Trace> 

Обновление

@ По совету Робина Зигмонда, я пробовал разные скобки, но безуспешно

main = trace "test" (return "main is called")
main = return(trace "test" "main is called") -- Joins the outputs into one string

1 Ответ

9 голосов
/ 10 апреля 2020

Во-первых, общее представление о том, как 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.

...