Как записать график звонков в Elm? - PullRequest
0 голосов
/ 15 ноября 2018

Пожалуйста, помогите, это сводит меня с ума безумие!

Как мне сделать так, чтобы Elm записывал график звонков?

Звучит просто, не так ли?Функция Debug.log должна сделать это тихо легко.Но нет, как я ни стараюсь, я просто не могу заставить Элма записывать события в правильном порядке.Я схожу с ума здесь ...


Давайте возьмем тривиальную функцию, подобную этой:

factorial : Int -> Int
factorial n = if n < 2 then 1 else n * factorial (n-1)

Что я хочу сделать, это написатьпользовательская функция trace, поэтому я могу сделать что-то похожее на

factorial n = trace ("factorial " + toString n) (if n < 2 ...)

, и она будет регистрировать что-то вроде

factorial 3: ENTER
factorial 2: ENTER
factorial 1: ENTER
factorial 1: 1
factorial 2: 2
factorial 3: 6

, чтобы вы могли видеть, как она вводит каждую функцию, и вы можете видетьвозврат из каждой функции (и какое значение она на самом деле вернула).


Что не работает:

  • Очевидная первая попытка сделать что-то вроде

    trace : String -> x -> x
    trace label x =
      let
        _ = Debug.log label "ENTER"
        _ = Debug.log label x
      in x
    

    Но я не думаю, что это когда-нибудь сработает.Поскольку Элм строг (?), x был оценен еще до того, как вы даже позвонили trace.Таким образом, все следы печатаются в обратном направлении.

  • Хорошо, давайте сделаем ввод тогда функцией:

    trace : String -> (() -> x) -> x
    trace label fx =
      let
        _ = Debug.log label "ENTER"
        x = fx ()
        _ = Debug.log label x
      in x
    

    Это действительно, действительно, выглядит так, как будто должно работать идеально.Но каким-то образом удается распечатать вход и выйти вместе , а затем все подчиненные вызовы, что, очевидно, неправильно.

  • Я особенно обеспокоентем фактом, что

    let
      _ = Debug.log label "ENTER"
      x = fx ()
    in x
    

    печатает все входы вперед, а идентичное выражение

    let
      _ = Debug.log label "ENTER"
    in fx ()
    

    печатает все входы назад.(??!) Я думаю, это то, что я получаю, пытаясь контролировать порядок побочных эффектов в чисто функциональном языке программирования ...

  • Хорошо, давайте сделаем так,затем блок:

    trace label fx =
      case Debug.log label "ENTER" of
        _ -> case Debug.log label (fx ()) of
          x -> x
    

    Нет, это печатает все задом наперед.Ну, это странно.Что если я просто поменяю местами оба выражения?... Нет, эти распечатки вводят + выходят вместе, за которыми следуют дочерние вызовы.

  • ОК, давайте разберемся.Lambdas FTW!

    trace label fx = Debug.log label ((\ _ -> fx ()) (Debug.log label "ENTER"))
    

    Это множество всех выходов, за которыми следуют все входы.Я просто поменяю местами выражения:

    trace label fx = (\ x -> (\ _ -> x) (Debug.log label "ENTER")) (Debug.log label (fx ()))
    

    Без кубиков.Это печатает ввод + выход для каждой группы вызовов снова вместе.

  • Умм ...

Серьезно, там должно быть способ заставить это работать!>_< Пожалуйста, помогите ... :'{

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Используя 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"
]

Обратите внимание, что этот писатель не оптимизирован (он объединяет списки, чёрт!), Но идея проверена и верна

0 голосов
/ 16 ноября 2018

Попробуйте:

trace : String -> (() -> x) -> x
trace label fx =
  let
    _ = Debug.log label "ENTER"  
  in
    let
      x = fx ()
      _ = Debug.log label x
    in 
      x

Похоже, что вы получите желаемый результат.

В качестве альтернативы, поскольку Debug.log возвращает второй аргумент, вы также можете написать следующее, что немногокороче:

trace : String -> (() -> x) -> x
trace label fx =
  let
    _ = Debug.log label "ENTER"  
  in
    let
      x = fx ()
    in 
      Debug.log label x

Если посмотреть на сгенерированный код, кажется, что компилятор переупорядочил объявления в блоках let.Использование вложенного блока let, похоже, убеждает компилятор не переупорядочивать объявления.

Если объявления внутри блока let не имеют каких-либо зависимостей, компилятор может переупорядочить их, как это нене изменяйте значение, возвращаемое функцией.Кроме того, если переменные объявлены не по порядку в пределах блока let, компилятор отсортирует их в правильном порядке.Возьмем, к примеру, следующую функцию:

silly : Int -> Int
silly x =
    let
        c = b
        b = a
        a = x
    in
        c * c

Компилятор Elm не может сгенерировать три назначения в блоке let в том же порядке, в котором они были объявлены: он не может вычислить c, не зная сначала, что b есть.Глядя на сгенерированный код для этой функции, я вижу, что назначения отсортированы в порядке, так что выходное значение рассчитывается правильно.Что бы вы ожидали, если бы вы поместили Debug.log вызовов в середине этой функции?

...