logging - 如何在 Elm 中记录调用图?

标签 logging elm

请帮忙,这让我彻底疯狂!

如何让 Elm 记录调用图?

听起来很简单,不是吗? Debug.log函数应该使这个安静变得容易。但是不,尽管我尽力了,我只是无法强制 Elm 按正确的顺序记录事件。我已经失去理智了...

<小时/>

让我们看一个像这样的简单函数:

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
    

    但我认为这永远不会起作用。由于 Elm 是严格的(?),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 ()
    

    向后打印所有输入。 (?!)我想这就是我尝试在纯函数式编程语言中控制副作用顺序所得到的结果......

  • 好吧,让我们将其设为 case-block:

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

    不,这会向后打印所有内容。嗯,这很奇怪。如果我只是交换两个 case 表达式会怎么样? ...不,它会一起打印 Enter+exit,然后是子调用。

  • 好吧,让我们开始硬核吧。 lambda 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 ()))
    

    没有骰子。这会再次打印每个调用组的 Enter+exit。

  • 嗯...

说真的,一定有办法让它发挥作用! >_<请帮助... :'{

最佳答案

试试这个:

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 block 内的声明进行了重新排序。使用嵌套的 let block 似乎可以说服编译器不要重新排序声明。

如果 let block 中的声明没有任何依赖项,则编译器可以自由地对它们重新排序,因为它不会更改函数返回的值。此外,如果变量在 let block 中声明为无序的,编译器会将它们排序为正确的顺序。以下面的函数为例:

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

Elm 编译器无法按照声明的顺序在 let block 中生成三个赋值:在不知道 b 是什么的情况下,它无法计算 c 是。查看此函数的生成代码,我可以看到分配已按顺序排序,以便正确计算输出值。如果您将 Debug.log 调用放在该函数的中间,您会期望发生什么?

关于logging - 如何在 Elm 中记录调用图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53327417/

相关文章:

java - Log4j2 api在OSGi环境下找不到Log4j2核心

c# - Microsoft azure 中的角色未记录到 WADLogsTable

榆树在新标签页中打开网址

c++ - 如何在 C++ 中创建日志?

使用配置文件记录 Python 3.2 导致 KeyError : 'formatters' on Raspbian

elm - Elm 中 Maybe a -> Svg Msg 类型的函数中没有 Nothing 时返回什么

elm - 表达式 "l =\m -> ..."中的\m 是什么意思?

elm - Cmd.map 是将 Elm SPA 拆分为模块的正确方法吗?

html - 在 Elm 中使用 Color 对象更改 HTML 元素颜色的标准方法是什么?

python - 如何禁用标准错误流的日志记录?