【问题标题】:What's wrong with this Julia swap! macro?这个 Julia 交换有什么问题!宏?
【发布时间】:2014-09-07 01:03:16
【问题描述】:

我正在尝试在 Julia 中编写一个简单的 swap! 宏,以了解宏系统。到目前为止,这是我的代码:

macro swap!(x, y)
    quote
        local tmp = $(esc(y))
        $x = $(esc(y))
        $y = tmp
    end
end

a = 1
b = 2

@swap!(a, b)

# prints: a: 1, b: 2
println("a: $a, b: $b")

这运行没有错误,但实际上并没有改变值。 Julia 似乎没有一个只扩展宏而不执行它们的函数(据我所知),所以这很难调试。

REPL 中的等效引用似乎按预期工作:

julia> a = 1
1

julia> a_sym = :a
:a

julia> a_sym
:a

julia> b = 2
2

julia> b_sym = :b
:b

julia> eval(quote
       tmp = $a_sym
       $a_sym = $b
       $b_sym = tmp
       end)
1

julia> a
2

julia> b
1

我做错了什么?

【问题讨论】:

  • 你确定a, b = b, a 分配不够吗?
  • @juliom 当然可以,但我的目的是学习 Julia 宏。
  • Julia 确实有宏扩展。调用它虽然有点棘手。 macroexpand(:(@swap x, y))

标签: macros julia


【解决方案1】:

我认为您可能想要类似下面的内容,但被hygiene 搞砸了。诀窍是正确逃脱。

macro swap(x,y)
   quote
      local tmp = $(esc(x))
      $(esc(x)) = $(esc(y))
      $(esc(y)) = tmp
    end
end

这是展开后的样子

julia> macroexpand(quote @swap(x,y) end)
quote  # none, line 1:
    begin  # none, line 3:
        local #189#tmp = x # line 4:
        x = y # line 5:
        y = #189#tmp
    end
end

效果

julia> x
1

julia> y
2

julia> @swap(x,y)

julia> x
2

julia> y
1

相比之下,您的宏在一个赋值中正确地转义了 y,但在其他 2 个语句中却没有,因此设置了引入变量的值,而不是预期的 x 和 y。

julia> macroexpand(quote @swap!(x,y) end)
quote  # none, line 1:
    begin  # none, line 3:
        local #208#tmp = y # line 4:
        #209#x = y # line 5:
        #210#y = #208#tmp
    end 
end

【讨论】:

  • 我已编辑答案以从宏中删除 return 语句。这是不必要的,也是错误的。它不是从宏返回,而是将一个 return 语句拼接到你使用宏的任何上下文中!这意味着如果您在函数中使用了它,那么该函数将在@swap 之后立即返回! begin 块的值就是它最后一个表达式的值。
【解决方案2】:

根据这篇帖子 https://rosettacode.org/wiki/Generic_swap ,看来 Julia 不需要任何临时变量。

a, b = b, a

所以,我们可以更轻松地编写它。

【讨论】:

    【解决方案3】:

    我从来没有听说过 Julia,但因为它的名字听起来很酷,所以我突然出现了。经过验证且真实的交换模式是:

    swap(a, b)
        tmp <- a
        a <- b
        b <- tmp
    

    你的代码看起来像这样(除非我不理解 Julia,在这种情况下我会很乐意删除这个答案)...

    swap(a, b)
        tmp <- b
        a <- b
        b <- tmp
    

    它记录了 b 的值,然后通过在其上写入 b 来丢失 a 的值,然后将 b 设置为存储在 tmp 中的原始值。

    【讨论】:

    • 啊哈,看出来了!这还不足以修复它,但它肯定是代码的问题。
    【解决方案4】:

    虽然接受的答案工作得很好,但我认为使用 temp 变量可能会错过宏的一些概念功能。我会这样写...

    macro swap!(x,y)
      quote
        $(esc(x)) = $(eval(y))
        $(esc(y)) = $(eval(x))
        return
      end
    end
    

    然后

    julia> test1 = 1
    1
    
    julia> test2 = 2
    2
    
    julia> macroexpand(:(@swap! test1 test2))
    quote  # none, line 3:
        test1 = 2 # line 4:
        test2 = 1 # line 5:
        return
    end
    

    由于宏返回一个将在运行时评估的字符串/表达式,您不妨跳过临时变量并将所需的值直接放入返回的字符串中。

    【讨论】:

    • 这只适用于全局范围。您通常不想使用eval,尤其是在宏中。
    • 有趣。我今天早上才了解宏,你的评论让我解决了多个误解。感谢您指出这一点。
    • 另请参阅my comment above,了解宏中引用的 return 语句。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多