【问题标题】:How to interpolate into a Julia "for" expression?如何插入 Julia“for”表达式?
【发布时间】:2026-01-23 21:40:01
【问题描述】:

我正在编写一个宏 @vcomp向量理解),它基于 Python 的列表理解,带有一个条件子句,以简洁的方式过滤元素。

macro vcomp(comprehension::Expr, when::Symbol, condition)
    comp_head, comp_args = comprehension.head, comprehension.args
    comp_head ∉ [:comprehension, :typed_comprehension] && error("@vcomp not a comprehension")
    when ≠ :when && error("@vcomp expected `when`, got: `$when`")
    T = comp_head == :typed_comprehension ? comp_args[1] : nothing
    if VERSION < v"0.5-"
        element  = comp_head == :comprehension ? comp_args[1] : comp_args[2]
        sequence = comp_head == :comprehension ? comp_args[2] : comp_args[3]
    else
        element  = comp_head == :comprehension ? comp_args[1].args[1] : comp_args[2].args[1]
        sequence = comp_head == :comprehension ? comp_args[1].args[2] : comp_args[2].args[2]
    end
    result = T ≠ nothing ? :($T[]) : :([])
    block = Expr(:let, Expr(:block,
                Expr(:(=), :res, result),
                Expr(:for, sequence,
                    Expr(:if, condition,
                        Expr(:call, :push!, :res, element))),
                :res))
    return esc(block)
end

这样使用:

julia> @vcomp Int[i^3 for i in 1:10] when i % 2 == 0
5-element Array{Int64,1}:
    8
   64
  216
  512
 1000

扩展至此:

julia> macroexpand(:(@vcomp Int[i^3 for i in 1:15] when i % 2 == 0))
:(let
        res = Int[]
        for i = 1:15
            if i % 2 == 0
                push!(res,i ^ 3)
            end
        end
        res
    end)

我期待能够像这样写block

block = quote
    let
        res = $result
        for $sequence
            if $condition
                push!(res, $element)
            end
        end
        res
    end
end

这给出了以下错误:

  • ERROR: syntax: invalid iteration specification

而不是我想出的方式:

block = Expr(:let, Expr(:block,
            Expr(:(=), :res, result),
            Expr(:for, sequence,
                Expr(:if, condition,
                    Expr(:call, :push!, :res, element))),
            :res))

但是我能够直接使用Expr(:for, ...) 来完成它,如上所示,据我所知,这是一个解析器错误(这是一个错误吗?)。我也一直找不到这种插值的例子,这就是我尝试过的:

julia> ex₁ = :(i in 1:10)
:($(Expr(:in, :i, :(1:10))))

julia> ex₂ = :(i = 1:10)
:(i = 1:10)

julia> quote
           for $ex₁
ERROR: syntax: invalid iteration specification

julia> quote
           for $ex₂
ERROR: syntax: invalid iteration specification

构造整个表达式并检查它:

julia> ex₃ = quote
           for i in 1:10
               print(i)
           end
       end
quote  # none, line 2:
    for i = 1:10 # none, line 3:
        print(i)
    end
end

julia> ex₃.args
2-element Array{Any,1}:
 :( # none, line 2:)
 :(for i = 1:10 # none, line 3:
        print(i)
    end)

julia> ex₃.args[2].args
2-element Array{Any,1}:
 :(i = 1:10)
 quote  # none, line 3:
    print(i)
end

julia> ex₃.args[2].args[1]
:(i = 1:10)

julia> ex₃.args[2].args[1] == ex₂    # what's the difference then?
true

这可行,但可读性较差:

julia> ex₄ = Expr(:for, ex₁, :(print(i)))
:(for $(Expr(:in, :i, :(1:10)))
        print(i)
    end)

julia> ex₅ = Expr(:for, ex₂, :(print(i)))
:(for i = 1:10
        print(i)
    end)

julia> eval(ex₃)
12345678910
julia> eval(ex₄)
12345678910    
julia> eval(ex₅)
12345678910    

有没有办法可以改用更简洁的语法?与我预期编写的相比,我发现当前的实现难以阅读和推理。

【问题讨论】:

  • 这有点不必要,不是吗?有很多方法可以模拟仍然是单行的条件列表理解。例如使用逻辑索引和复合语句:[i^3 for i = (A = 1:10; A[A % 2 .== 0])],使用条件运算符:deleteat!((A = [i % 2 == 0 ? i^3 : nothing for i in 1:10]; A), find(A .== nothing)),或使用过滤函数:[i^3 for i in filter((x) -&gt; x%2 == 0, 1:10)]。无需经历所有麻烦...
  • @TasosPapastylianou one liner简洁 不一样,即使我用了你的一种方法,它也会结合宏(所以我可以按照我想要的方式编写它),因为我不想写你建议的内容(一遍又一遍),而这正是宏的目的。这个宏是我学习元编程的一个练习,所以我认为这是值得的麻烦,从你的建议我只会考虑最后一个,filter,因为它的性能与 Julia v0 中的[i^3 for i in 1:n if i % 2 == 0] 相当.5.0-rc0 以及我的@vcomp 宏。
  • 好吧,很公平。我同意“oneliner!= 简洁”,但我认为以上所有三个版本,如果分成两行,变得非常可读且相当简洁,而使用晦涩的宏对于原始作者以外的任何人来说都不太容易理解。但我同意,这是一个非常好的宏,可以用来试验语言:) 不过官方版本很快就会推出,我不知道为什么它从一开始就没有被认为是必不可少的。

标签: metaprogramming julia expression-trees abstract-syntax-tree


【解决方案1】:

首先,我相信对 Julia 的理解会出现在 Julia 中(在 v0.5 中?)。

回答您的问题:解析器希望能够验证其输入在语法上是否正确,而无需查看插入的实际值。试试例如

x, y = :i, :(1:10)
quote
    for $x = $y
    end
end

现在解析器可以识别语法的相关部分。 (如果你改用for $x in $y,你应该得到相同的AST。)

【讨论】:

  • 公平地说,我看不出为什么在降低而不是解析期间不能产生这个错误的原因。 let x + y 也是错误的,但是这个错误是一个降低错误。
  • 带警卫的 Comorehensions 现已推出 0.5-pre。
  • 感谢您的解释,我同意@Fengyang Wang 的观点,这应该是一个降低错误,我会在 julia-dev 询问。