1

Ich schrieb ein Makro @vcomp (Vektor Verständnis) basierend auf Python Listen-Comprehensions mit einer bedingten Klausel, um Elemente in einer prägnanten Art und Weise zu filtern.Wie interpoliert man in einen Julia "for" Ausdruck?

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 

wie folgt verwendet:

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

auf das sich diese erweitern:

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) 

Ich hatte erwartet, der Lage sein, dies zu schreiben block wie:

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

Welche der folgenden Fehler gibt:

  • ERROR: syntax: invalid iteration specification

Statt der Art und Weise kam ich mit:

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

aber ich war in der Lage, es zu tun Expr(:for, ...) direkt, wie oben gezeigt, und so weit ich das verstehe, ist das ein Parserfehler (ist das ein Bug?). Ich habe auch nicht in der Lage gewesen, Beispiele für diese Art von Interpolation zu finden, das ist, was ich versucht habe:

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 

Construct ganzen Ausdruck und prüfen:

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 

Das funktioniert aber weniger lesbar:

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  

Gibt es eine Möglichkeit, die eher knappe Syntax stattdessen verwenden? Ich finde die aktuelle Implementierung schwierig zu lesen und zu begründen im Vergleich zu dem, was ich zu schreiben erwartet hatte.

+0

das ist alles ein bisschen unnötig, nicht wahr? Es gibt viele Möglichkeiten, das Verständnis der bedingten Listen zu emulieren, die immer noch Einzeiler sind. z.B. mit logischer Indizierung und einer zusammengesetzten Anweisung: '[i^3 für i = (A = 1:10; A [A% 2. == 0])] ', mit dem bedingten Operator:' deleteat! ((A = [i% 2 == 0? i^3: nichts für i in 1:10]; A), finde (A. == nothing)) 'oder benutze die Filterfunktion:' [i^3 für i im Filter ((x) -> x% 2 == 0, 1:10)]. Keine Notwendigkeit, durch all diese Probleme zu gehen ... –

+0

@TasosPapastylianou * ein Liner * ist nicht das gleiche wie ** prägnant **, auch wenn ich eine Ihrer Methoden verwendet, wäre es in Kombination mit einem Makro (so kann ich schreibe es so, wie ich es will), da ich nicht das schreiben möchte, was du vorgeschlagen hast (und immer wieder) und genau das ist der Zweck von Makros.Dieses Makro war eine Übung für mich, um Metaprogrammierung zu lernen, also denke ich, dass es die Mühe wert war, von deinen Vorschlägen würde ich nur das letzte betrachten, das mit 'Filter', da seine Leistung mit' [^^ für ich in 1: n wenn ich% 2 == 0] 'in Julia v0.5.0-rc0 und auch zu meinem' @ vcomp' Makro. – SalchiPapa

+0

Nun, fair genug. Ich stimme zu, dass "oneliner! = Prägnant", aber ich denke, dass alle drei obigen Versionen, wenn sie über zwei Zeilen verteilt sind, sehr lesbar und relativ prägnant werden, während die Verwendung eines obskuren Makros für jeden anderen als den ursprünglichen Autor weniger verständlich wäre. Aber ich stimme zu, es ist ein sehr nettes Makro, mit dem man experimentieren kann, um mit der Sprache zu experimentieren :) Es ist gut, dass bald eine offizielle Version kommt, ich weiß nicht, warum es von Anfang an nicht als wesentlich angesehen wurde . –

Antwort

5

Zunächst einmal glaube ich, dass Nachforschungen mit Wachen kommen Julia (in v0.5?).

Um Ihre Frage zu beantworten: Der Parser möchte in der Lage sein zu überprüfen, dass seine Eingabe syntaktisch korrekt ist, ohne in den tatsächlichen Wert zu interpolieren. Versuchen Sie zB

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

Jetzt kann der Parser die relevanten Teile der Syntax erkennen. (Und Sie sollten die gleiche AST erhalten, wenn Sie stattdessen for $x in $y verwenden.)

+1

Um fair zu sein, sehe ich keinen Grund, warum dieser Fehler nicht beim Parsen, sondern beim Senken erzeugt werden kann. 'let x + y' ist auch falsch, aber dieser Fehler ist stattdessen ein Senkungsfehler. –

+2

Comorensionen mit Wachen sind jetzt 0.5-pre. –

+0

Danke für deine Erklärung, ich stimme @Fengyang Wang darin zu, dass dies ein Senkfehler sein sollte, ich werde bei julia-dev danach fragen. – SalchiPapa