Используя Debug.log
, вы пытаетесь сделать что-то нечистое на чистом языке. Даже если вы дойдете до точки, где она работает, как заметил @Luke Woodward, я бы не стал полагаться на это, потому что вывод журнала мог бы очень хорошо переключаться между версиями компилятора.
Вместо этого мы могли бы создать уменьшенную монаду Writer, чтобы сохранять журналы с сохранением состояния в порядке их появления.
type Writer w a = Writer (a, List w)
runWriter : Writer w a -> (a, List w)
runWriter (Writer x) = x
pure : a -> Writer w a
pure x = Writer (x, [])
andThen : (a -> Writer w b) -> Writer w a -> Writer w b
andThen f (Writer (x, v)) =
let (Writer (y, v_)) = f x
in Writer (y, v ++ v_)
log : String -> a -> Writer String a
log label x = Writer (x, [label ++ ": " ++ Debug.toString x])
Затем вы можете добавить его в свою факториальную функцию, что означает, что теперь функция должна будет возвращать Writer String Int
вместо Int
:
factorial : Int -> Writer String Int
factorial n =
let logic =
if n < 2 then
pure 1
else
factorial (n-1)
|> andThen (\z -> pure (n * z))
in
log ("factorial " ++ Debug.toString n) "ENTER"
|> andThen (\_ -> logic)
|> andThen (\result -> log ("factorial " ++ Debug.toString n) result)
Несмотря на то, что это выглядит более громоздким и навязчивым (синтаксис Elm не так удобен для монад, как Haskell), он будет давать вам предсказуемые результаты каждый раз, а не зависеть от ненадежных побочных эффектов.
Результат выполнения factorial 3 |> runWriter |> Tuple.second
:
[ "factorial 3: \"ENTER\""
, "factorial 2: \"ENTER\""
, "factorial 1: \"ENTER\""
, "factorial 1: 1"
, "factorial 2: 2"
, "factorial 3: 6"
]
Обратите внимание, что этот писатель не оптимизирован (он объединяет списки, чёрт!), Но идея проверена и верна