Julia: a, b... = [1,2,3]

Erstellt am 20. März 2013  ·  58Kommentare  ·  Quelle: JuliaLang/julia

Dies wäre eine sehr schöne Syntax, um Kopf und Ruhe zu nehmen. Ebenso könnte a..., b = [1,2,3] gut sein, um die Anfangselemente in a und das Endelement in b zu schlürfen.

design lowering

Hilfreichster Kommentar

Heute wollte ich diese Syntax wieder. Ich denke, das sollten wir bedenken.

Alle 58 Kommentare

Sollen wir tail(itr, state) zum Iterationsprotokoll hinzufügen, um eine Sammlung mit Elementen ab dem angegebenen Zustand zu erhalten?

In manchen Fällen wäre das einfach, wird es aber nicht immer. Ein Rest{T,S}(itr::T,state::S) Typ zu haben, der einen Iterator mit einem Zustand umschließt und Ihnen erlaubt, den Rest zu iterieren, könnte den Zweck erfüllen.

Ah, natürlich macht der Drop Iterator bereits etwas sehr Ähnliches.

Rest könnte ein besserer Name für diesen Iterator sein.

Nun, sie sind tatsächlich ein bisschen anders. Drop benötigt eine Anzahl von zu überspringenden Elementen. Rest würde von einem bestimmten Zustand aus starten, was die Implementierung im Grunde einfach macht.

Ah, das stimmt, aber der Typ Rest kann beide Zwecke erfüllen, es kommt nur darauf an, wie Sie dorthin gelangen – indem Sie einen expliziten Zustand oder eine Reihe von Werten überspringen.

Wäre es damit nicht möglich, varargout Matlab-Stil zu simulieren? Ich denke, obwohl es möglich ist, sollte es vermieden werden, da es das Typensystem verwüsten würde.

Nein, dies sagt der Funktion nicht, wie viele Ausgaben angefordert werden. Und tatsächlich, in a,b = f() , solange f 2 oder mehr Werte zurückgibt, wird dies funktionieren und den Rest einfach löschen.

Es führt jedoch dazu, dass dem Iterator mitgeteilt wird, wie viele Argumente erforderlich sind. Es scheint, dass Sie die Funktion als Fortsetzungs-Iterator schreiben könnten, um den Varargout von Matlab schwach zu simulieren (die vernünftige Version, bei der Sie nur faule Berechnungen durchführen).

Wir könnten Matlabs Varargout im Allgemeinen unterstützen, wenn wir es faul machen und das Protokoll für die Destrukturierung ein wenig ändern. Die Idee entstand in der Diskussion über Faktorisierungsobjekte. Dh wenn a,b = x einen einzelnen destrukturierenden Aufruf von x , was eine Art boolesche Maske ergibt, aus der Werte erzeugt werden sollen. Andererseits bin ich mir nicht sicher, ob wir wirklich ein vollständig allgemeines Varargout wollen, da es ein bisschen seltsam ist, dass sich die Ausgaben vollständig ändern können, je nachdem, wie viele von ihnen angefordert werden.

Bevor dies umgesetzt wird, wäre es vielleicht gut zu prüfen, ob ein allgemeinerer Ansatz zur Destrukturierung von Aufgaben begrüßt wird. Grundsätzlich kann jede Funktion f mit einer festen bijektiven Umkehrfunktion inv(f) verwendet werden, um zu schreiben

f(a, b) = c
-> a, b = inv(f)(c)

Beispiele für f sind Tupelkomposition und Listenkomposition aus Kopf und Schwanz
f(a,b) = (a,b)
aber auch sagen
(sign(x), abs(x))
und
((a,c), (b,c))
haben solche Umkehrungen, die durch Anwendung der Regel gefunden werden können
inv(f*g) = inf(g)*inv(f)
oder explizit angegeben werden.

Ja, das ist möglich, aber es ist keine neue Funktion, die durch die a,b... = x Syntax hinzugefügt wird. Du kannst es schon tun. Lassen Sie die Funktion einfach einen Iterator zurückgeben, der Werte berechnet, wie next aufgerufen wird. Dann berechnet a,b = f() 2 Werte, a,b,c = f() berechnet 3 usw., ohne dass ... benötigt wird.
Das eigentliche Problem ist der Fall eines einzelnen Ergebnisses, a = f(x) , das nur das Ganze zuweist und keine Destrukturierung stattfindet. Wenn das kaputt geht, wird das Erstellen von Funktionen schwierig.

Heute wollte ich diese Syntax wieder. Ich denke, das sollten wir bedenken.

(Match.jl hat das)

Hier gibt es einige Designprobleme, vor allem: Was sollte der Typ von b (oder a ) sein – Array, Tupel oder Iterator? Da wir dies nicht angesprochen haben, scheint es am besten, dies auf 1.0 zu erhöhen.

Hier ist ein interessantes Beispiel, das das Problem anspricht.

julia> a, b, c = countfrom()
Base.Count{Int64}(1,1)

julia> a, b, c
(1,2,3)

Dies funktioniert wie erwartet, aber was ist, wenn "Splatting" für eine der Variablen verwendet wurde?

Für den Fall, dass c "gespritzt" wurde, wäre es sinnvoll, einen Iterator mit start zurückzugeben, der den "aktuellen" Iteratorwert zurückgibt und next / done entspricht dem des vorhandenen Iterators.
Was aber, wenn b "bespritzt" wurde? Würde der Code unbegrenzt laufen? Ein Iterator könnte nützlich sein, wenn die letzte Variable "splatted" ist, aber sonst könnte es verwirrender werden.

Für den Fall, dass ein Iterator jedoch nicht die richtige Wahl ist, scheint die Frage der Mutabilität (Tupel vs. Array) durchaus diskussionswürdig zu sein.

Ja, es scheint, dass im allgemeinen Fall die Rückgabe eines Rest Iterators erforderlich sein könnte.

Dies ist eine Funktion, die ich ein paar Mal vermisst habe. FWIW, Python scheint beim Entpacken das Äquivalent von collect zu tun und gibt eine Liste zurück. Ich mag das nicht, ich denke, es sollte einen Typ zurückgeben, der dem entspricht, was entpackt wird.

Könnte der Rückgabetyp an den Typ des Iterables delegiert werden? D. h. übersetzen

a, b, c..., d, e = f
->
a = first(f); b = second(f);
e = last(f); d = secondlast(f);
c = slurp(f, 3, end-2)

Wenn das Iterable unendlich ist, sollte der Aufruf von last(f) einen Fehler auslösen.

Ich denke, das Problem betrifft iterierbare Dinge, die nicht unendlich sind, aber eine unbestimmte Länge haben und / oder nur der Reihe nach wiederholt werden können – Sie können den letzten Artikel nicht erhalten, bevor Sie die vorherigen gesammelt haben.

Ist dies nicht eine 1.x-Möglichkeit? Dies ist derzeit eine ungültige Syntax.

Der Python-Ansatz könnte einen Blick wert sein, indem Sie * durch ... ersetzen. (Ich sehe, dass Dalum den Einzelheiten skeptisch gegenübersteht – aber ist es nicht gut, dem Ansatz von Varargs etwas zu folgen, bei dem args... zu einer Folge von Werten wird? Ich könnte das falsch verstehen.)

Wie auch immer, Python erlaubt auch a, *b, c = range(5) (dh a, b..., c = 0:4 ). Wir möchten diesen Ansatz vielleicht nicht kopieren, aber es lohnt sich, einen Blick auf die (ziemlich kurze) Spezifikation zu werfen, da dies eine Bedeutung dieser Art des Entpackens ist, die bereits weit verbreitet ist. (Und ja, diese Art des Auspackens ist sehr nützlich und lesbar, IMO.)

Selbst wenn ich diesen PEP lese, verstehe ich nicht wirklich, was sie tun. Die Hauptfrage, die wir haben, ist, ob dies eifrig oder faul sein sollte. Der Python-Ansatz scheint eifrig zu sein?

Vielleicht ist es sinnvoll, hier zwei verschiedene Klassen von Iterables zu haben? Wenn wir eine Klasse definieren, für die es (genug) Sinn macht, eifrig zu sein (sicherlich Tupel, vielleicht Vektoren, möglicherweise einige andere Dinge, aber sicherlich nicht potenziell unendliche Folgen), könnten wir a, b..., c zulassen – sonst könnte das sein ein Fehler (die Standardeinstellung).

(Ansonsten könnten wir, um faul zu sein und es trotzdem zuzulassen, etwas wie Future , aber das scheint irgendwie übertrieben und wahrscheinlich sowieso kein sehr nützlicher Anwendungsfall?)

Es ist im Allgemeinen keine gute Idee, generischen Code zu schreiben, um die Bedeutung basierend auf dem Typ zu ändern.

Aber ja, die Python-Definition enthält die Länge der iterablen rhs (dh die rhs muss mindestens so viele Elemente haben wie die lhs), und obwohl ich sie nur oberflächlich betrachtet habe, scheint die Implementierung eifrig zu sein.

@StefanKarpinski Ich denke, das hängt davon ab, was Sie meinen. Aber ja, ich denke, es macht Sinn, das selbst eifrig zu machen – und es sich „genau wie“ Varargs (die schließlich ein sehr enger Verwandter sind) verhalten zu lassen.

Das scheint die naheliegende Version zu sein. Beachten Sie jedoch, dass varargs Tupel erzeugt, was für diese Art der Verwendung etwas fragwürdig ist. Vielleicht möchten wir stattdessen einen Vektor sammeln.

Einverstanden. Genau das ist auch in Python der Fall: In Varargs werden Tupel verwendet, während in Zuweisungen Listen verwendet werden.

Dadurch können wir auch Funktionssignaturen schreiben wie:

julia> foo((a, bs...), c, ds...) = bs
ERROR: syntax: invalid assignment location "bs..."

Was nützlich wäre für zB https://github.com/JuliaDiff/ChainRulesCore.jl/issues/128#issuecomment -586716291

Ich denke, das führt uns zu einer Antwort, wie sich a, bs... = <expr> verhalten muss (was ohnehin schon festgestellt wurde):

  • foo(a, bs...) macht bereits bs Tupel
  • foo((a, bs...)) sollte sich genauso verhalten
  • (a, bs...) = <expr> sollte sich genauso verhalten

Ich denke das wäre auch interessant

a, b, c[3:4] = [1, 2, 3, 4, 5];

#a= 1
#b= 2
#c= [3, 4]

Ich hätte eine starke Erwartung, dass diese Syntax einem Slice zugewiesen wird.

@cindRoberta Siehe meinen Kommentar in Ihrer Ausgabe. Diese Syntax wird nicht funktionieren, da sie bereits eine Bedeutung hat.

Dies scheint jedoch möglich:

a, b, c[3:4]... = [1, 2, 3, 4, 5];

Könnte nur mein mentales Modell sein, das janky ist, aber das erscheint mir sehr verwirrend. Dh, es sieht so aus, als ob Sie c[3] und c[4] zuweisen würden, nicht dass Sie rhs[3:4] c zuweisen würden …?

Ich stimme dir zu, @mlhetland , das würde ich erwarten. Einen Index auf der linken Seite zu verwenden, um anzugeben, welcher Wert auf der rechten Seite genommen werden soll, ist sehr wenig intuitiv. Ich weise nur darauf hin, welche Syntax verfügbar ist.

Was wäre, wenn ein Symbol oder ein Token verwendet würde, um die Zuweisung an die Variable anstelle der Zuweisung an das Slice anzuzeigen?

Rust verwendet & um den Slice und die Zuordnung zum Slice anzugeben, was für mich ideal ist, in Julia wäre das wie folgt:

dem Slice zuordnen:

c = [1, 2, 3, 4, 5]
&c[3:4] = [6, 7]
# c = [1, 2, 6, 7, 5]

der Variablen zuordnen:

c[3:4] = [1, 2, 3, 4, 5]
# c = [3, 4]

oder mit dem Token ¬ (oder einem anderen Token)

c¬[3:4] = [1, 2, 3, 4, 5]
# c = [3, 4]

aber am nächsten an der Realität, ohne Konflikte, ist:

das funktioniert bei Julia schon, ich weiß, es ist eine Frage der Präferenz &

c = [1, 2, 3, 4, 5]
c[3:4] = [6, 7]
# c = [1, 2, 6, 7, 5]

zu variabel:

c¬[3:4] = [1, 2, 3, 4, 5]
# c = [3, 4]

es ist ein wenig verwirrend, ich weiß, aber es geht nicht anders und natürlich behalten die ... den Rest zuzuordnen

Nun, es scheint potentiell nützlich zu sein, den linken Seiten zu erlauben, die rechten zu zerlegen. Ich schätze, einer hätte es auch

§x["foo"] = y

bedeuten

x = y["foo"]

zum Beispiel? Vielleicht sogar die Attributsuche auf die gleiche Weise zulassen? (Und Funktionsaufrufe?)

Andererseits, wenn Sie genau sagen wollen, welche Teile Sie wollen, warum nicht diese Informationen auf der rechten Seite? Die Entpacksyntax ist praktisch, wenn die Teile weitgehend implizit sind, aber ich bin mir nicht sicher, ob ich denke, sagen wir

§a[1,3], §b[2,4] = c

ist besser als

a, b = c[1,3], c[2,4]

Ganz im Gegenteil. Es ist nicht nur ausführlicher, es ist zumindest für mich ziemlich verwirrend und widerspricht der allgemeinen Funktionsweise der Indexierung.

Andererseits weiß ich nicht, was die gewünschten Anwendungsfälle sind – ich spreche eindeutig nur für mich.

(Edit: Mit "ausführlicher" meinte ich, dass es mehr Zeichen verwendet, aber natürlich nicht. #sleepybrain)

@mlhetland gerade in meinem Ideal,

das & verweist auf eine vorab deklarierte Variable

a = [1, 2, 3, 4, 5]
print(&a[3:4]) # [3, 4]

und a[] erstellen ein Unterarray eines anderen Arrays

a[3:4] = [1, 2, 3, 4, 5] or
a¬[3:4] = [1, 2, 3, 4, 5]
print(a) # [3, 4]

nur das, vielleicht gibt es in Julia schon eine Funktion, die das macht, aber ich weiß es nicht


vielleicht geht das so

a = [1, 2, 3, 4, 5]
&a[3:4]¬[1:2] = [6, 7, 8, 9]
or ¬(&a[3:4])[1:2] = [6, 7, 8, 9]

# a = [1, 2, 6, 7, 5]

bei Julia:

a = [1, 2, 3, 4, 5]
a[3:4]¬[1:2] = [6, 7, 8, 9]
or ¬(a[3:4])[1:2] = [6, 7, 8, 9]

# a = [1, 2, 6, 7, 5]

lhs ist nur zum Destrukturieren geeignet, weil ich es in rhs tun kann:

a = [1, 2, 3, 4, 5][3:4]

aber bei der Destrukturierung nicht

Erstellen eines Unterarrays:

a, b, c[3:4] = [1, 2, 3, 4, 5] or
a, b, ¬c[3:4] = [1, 2, 3, 4, 5] or
a, b, c¬[3:4] = [1, 2, 3, 4, 5] # I prefer this
# a = 1, b = 2
# c = [3, 4]

Scheibe:

c = [6, 7, 8, 9]
a, b, &c[1:2]¬[3:5] = [1, 2, 3, 4, 5]
# a = 1, b = 2
# c = [3, 4, 5, 8, 9]

oder Anpassung an Julia Slice

c = [6, 7, 8, 9]
a, b, c[1:2]¬[3:5]= [1, 2, 3, 4, 5]
# a = 1, b = 2
# c = [3, 4, 5, 8, 9]

Es tut mir leid, wenn ich etwas übersehen habe, aber ich verstehe nicht, was die Vorschläge mit § mit diesem Problem zu tun haben. Auf jeden Fall denke ich, dass wir sehr gute Konstrukte haben, um Arrays auf komplizierte Weise aufzuteilen, die in die Semantik der Sprache passen. Die Einführung einer DSL, die dies auf dem LHS tut, scheint nicht ausreichend motiviert.

Im Gegensatz dazu würde die a, b... = Syntax einem allgemeinen Anwendungsfall dienen und die Funktionsweise von Funktionsargumenten widerspiegeln.

Die Splatting-Syntax macht auch Sinn bzgl. aktuelle Syntax/Semantik (wie zB in Python): Konzeptionell spritzt man eine noch nicht vorhandene Sequenz/Tupel in die lhs ein, deren Elemente dann durch die Zuweisung definiert werden. Es gibt gewissermaßen nur einen Weg zu interpretieren, was wohin gehört.

Ich würde das ... in a, b... = als schlürfen und nicht als spritzen verstehen (genau wie die ursprüngliche Ausgabe es ausdrückt und genauso funktioniert es für Funktionen).

Sicher. Mein Punkt war, dass Schlürfen konzeptionell das Einspritzen einer Reihe von Zielpositionen in die linke Seite ist. Es ist kein separater konzeptioneller Rahmen erforderlich, um es zu verstehen. „Vorgeben“, dass die Zielsequenz in gewissem Sinne existiert; es wird dann als eine Folge von Zielen für die Zuweisung eingestreut. Mein Hauptpunkt war einfach, dass es wirklich keinen Spielraum gibt, was es bedeutet und wie es sich verhält, was ich für eine gute Sache halte.

Diese Symmetrie zwischen Slurping und Splatting (in jedem Fall nur das Entfernen einer Reihe von Klammern aus einem Tupel) war zumindest immer mein mentales Modell (auch für das Äquivalent in Python). Ich finde es fast verwirrender, sie als getrennte Ideen zu behandeln; meiner Meinung nach ist es nur eine Frage von lhs vs. rhs (auch in Funktionsargumenten).

Es tut mir leid, wenn ich etwas übersehen habe, aber ich verstehe nicht, was die Vorschläge mit § mit diesem Problem zu tun haben. Auf jeden Fall denke ich, dass wir sehr gute Konstrukte haben, um Arrays auf komplizierte Weise aufzuteilen, die in die Semantik der Sprache passen. Die Einführung einer DSL, die dies auf dem LHS tut, scheint nicht ausreichend motiviert.

Im Gegensatz dazu würde die a, b... = Syntax einem allgemeinen Anwendungsfall dienen und die Funktionsweise von Funktionsargumenten widerspiegeln.

mein Vorschlag ist einfach zu verstehen, aber unkonventionell und auf Destrukturierung beschränkt (daher bezogen auf dieses Thema), nur:

die a¬[] erstellen ein untergeordnetes Array eines anderen Arrays

a¬[3:4] = [1, 2, 3, 4, 5] # I think this is better
or ¬a[3:4] = [1, 2, 3, 4, 5]
print(a) # [3, 4]

Scheibe wechseln

a = [1, 2, 3, 4, 5]
a[3:4]¬[1:2] = [6, 7, 8, 9]
or ¬(a[3:4])[1:2] = [6, 7, 8, 9]

# a = [1, 2, 6, 7, 5]

Erstellen eines Sub-Arrays aus der Destrukturierung:

a, b, c¬[3:4] = [1, 2, 3, 4, 5] # I think this is better
or a, b, ¬c[3:4] = [1, 2, 3, 4, 5]
# a = 1, b = 2
# c = [3, 4]

Slice aus Destrukturierung ändern

c = [6, 7, 8, 9]
a, b, c[1:2]¬[3:5] = [1, 2, 3, 4, 5]
# a = 1, b = 2
# c = [3, 4, 5, 8, 9]

Ist natürlich mit Vorsicht zu genießen und ich sage nicht, dass wir hier einfach der Mehrheitsmeinung folgen sollten, aber ich habe mich interessiert, was die Leute natürlich davon erwarten und habe eine kurze Umfrage zu Slack gemacht:

Screenshot from 2020-09-11 22-34-08

Ich war besonders überrascht, dass so viele Leute die Rückgabe eines Tupels für Arrays für die nützlichste aller Optionen hielten, da ich mir vorgestellt hätte, dass die Rückgabe eines Vektors im Allgemeinen bevorzugt wird. Wie beim Triage-Aufruf besprochen, kann es auch eine sehr praktikable Option sein, einen Fehler auszulösen, wenn der rhs kein Tupel ist, bis wir uns über alle anderen Fälle entschieden haben.

In Bezug auf andere Sprachen habe ich festgestellt, dass rost so etwas hat, aber als Teil ihrer allgemeineren Match-Syntax. Sie unterstützen nur das Schlürfen für Arrays (zumindest keine Tupel) mit [a, b @ ..] => ... . b ist dann ein "Slice", was ihr Typ für Ansichten ist, aber Slices sind standardmäßig unveränderlich, also müssen Sie mut explizit angeben, wenn b mutiert werden soll nachher. Da sich Pattern Matching aber stark von Destrukturierung bei Julia unterscheidet, weiß ich nicht, ob das wirklich vergleichbar ist.

Da wir derzeit Vektorausdrücke auf der linken Seite von Zuweisungen nicht zulassen, wäre ein spekulativer Vorschlag, diese Syntax auch für die Destrukturierung zu unterstützen, mit dem Unterschied, dass [a, b...] = itr immer den Rest von itr in a . sammelt Vektor, während für (a, b...) = itr b immer ein Tupel ist. Das funktioniert immer noch nicht für unendliche Iteratoren, aber mir scheint, dass sie in echtem Code ziemlich selten sind und ich denke, es ist vernünftig, den Rest mit Iterators.rest oder Iterators.drop explizit nachfragen zu müssen in diesen Situationen. @JeffBezanson , wäre daran interessiert, Ihre Meinung dazu zu hören.

Interessant. Ich kann meinen, alles zu Tupeln zu sammeln, weil es so Varargs so ähnlich wie möglich ist. Aber ich denke, diese Option ist schrecklich NICHT nützlich. Es gibt der Operation "Nehmen Sie den Schwanz dieser Datenstruktur und konvertieren Sie sie in ein Tupel" eine spezielle Syntax. Warum sollten Sie dafür Syntax haben? Es ist für praktisch alle Fälle außer Tupeln sehr langsam und typinstabil. Der Vergleich mit Varargs ist nicht so sinnvoll, wie es zunächst scheint, da wir immer zuerst Funktionsargumente in ein virtuelles Tupel verteilen müssen, um alle ihre Typen für den Versand zu untersuchen. Und in der Tat ist das Verteilen großer Sammlungen langsam. Es ist eine ziemlich häufige Leistungsfalle. Der Versuch, hier wie Varargs zu sein, würde also absichtlich diesen negativen Aspekt der Sprache kopieren.

Da sich Pattern Matching aber stark von Destrukturierung bei Julia unterscheidet, weiß ich nicht, ob das wirklich vergleichbar ist.

Ich denke, es ist fast dasselbe. Natürlich hat Rost andere Bedenken hinsichtlich der Veränderlichkeit, die es jedoch schwierig machen, ihn direkt zu kopieren.

Ja, in Anbetracht dessen denke ich, dass der beste Weg hier wahrscheinlich darin besteht, mit E zu gehen, dh einen Fehler für alles auszulösen, was kein Tupel ist, für 1.6, da die Rückgabe eines Tupels hier ziemlich unumstritten sein sollte und wahrscheinlich auch der häufigste Fall ist Leute möchten diese Syntax für verwenden. Das würde es uns ermöglichen, die anderen Fälle später noch einmal zu betrachten, wenn die Leute diese Syntax bereits ein wenig verwendet haben, damit wir dann vielleicht eine fundiertere Entscheidung treffen können.
Bleibt dann nur noch die Frage, auf was diese Syntax abgesenkt werden soll. Wir könnten Base.tail eine Methode hinzufügen, die auch einen Index zum Konsumieren akzeptiert, aber vielleicht wäre eine separate Funktion, die möglicherweise auch einen Iterationszustand akzeptiert, zukunftssicherer und erweiterbarer und ermöglicht klarere Fehlermeldungen. Base.rest kann zu verwirrend sein, da wir bereits Iterators.rest und dies wahrscheinlich eine andere API hätte, im Moment nenne ich es Base.slurp_rest , aber ich bin offen für Verbesserungsvorschläge Namen/APIs.

Falls sie von Interesse sind, hier sind einige E-Mails, in denen diese Fragen auf einer Mailingliste für Python-Entwickler im Jahr 2007 erörtert wurden. Ich bin mir nicht sicher, wie nützlich die Python-Perspektive ist, fand sie aber interessant.

Um die Semantik festzulegen, wäre es interessant zu sehen, welche konkrete Semantik die Leute dadurch ersetzen wollen.

ZB könnte ich mir vorstellen

a, b... = c

ersetzen

a, b = first(c), c[(begin+1:end)]

aber auch Variationen mit view , Verwerfen/Beibehalten der generalisierten Indizierung für b (zB wenn c::OffsetVector ), etc.

Es ist nicht klar, ob einer von diesen dem anderen vorzuziehen ist. Aus diesem Grund denke ich, dass die Verwendung eines expliziten Konstrukts auf dem RHS eine vernünftige Alternative ist.

Ja, ich stimme zu, dass es schwierig ist, eine Semantik zu finden, die für beliebige Array-Typen und Iteratoren gut funktioniert, aber ich denke, es wäre eine echte Schande, diese schöne Syntax ganz aufzugeben. https://github.com/JuliaDiff/ChainRulesCore.jl/issues/128#issuecomment -586716291 ist nur ein Beispiel, wo dies wirklich nützlich wäre, wenn es zumindest für Tupel funktioniert. Wenn wir diese Syntax vorerst nur für Tupel zulassen würden, sehe ich nicht, wie dies semantisch problematisch wäre.

Die Beschränkung auf Tupel wäre etwas verwirrend, da die a, b, c = rhs Syntax für alle iterierbaren Elemente funktioniert.

Wenn der Benutzer wirklich Tupel will, warum nicht einfach

f(t) = first(t), Base.tail(t) # please someone invent a snappy name for f
a, b = f(t)

Die Beschränkung auf Tupel wäre etwas verwirrend, da die Syntax a, b, c = rhs für alle iterierbaren Elemente funktioniert.

Es wäre kein Syntaxfehler, sondern nur ein Fehler, weil das Analogon von Base.tail nicht für beliebige Iterables definiert ist, was mir vernünftig erscheint, da letzteres auch nur für Tupel funktioniert.

Wenn der Benutzer wirklich Tupel will, warum nicht einfach

f(t) = first(t), Base.tail(t) # please someone invent a snappy name for f
a, b = f(t)

Sicher, aber Sie könnten genau das gleiche Argument gegen so ziemlich jede Syntaxfunktion vorbringen. Ich denke, was a, b... = t tut, sollte jedem sofort klar sein, der damit vertraut ist, wie Splatting und Slurping bei Funktionsaufrufen funktioniert. Besonders in Funktionssignaturen, wie im Beispiel von @oxinabox , finde ich es einfacher herauszufinden, was die Funktion mit der slurping-Syntax macht, als mit Base.tail . In diesem Beispiel würde diese Änderung das Schreiben von frule s mit ChainRulesCore.jl intuitiver und konsistenter mit rrule für Leute machen, die neue Regeln schreiben möchten.

es wird nur ein Fehler sein, weil das Analogon von Base.tail nicht für beliebige Iterables definiert ist, was mir vernünftig erscheint, da letzteres auch nur für Tupel funktioniert

Ich verstehe, dass Sie einen bestimmten Anwendungsfall im Sinn haben, aber aus der Diskussion scheint es, dass andere einen anderen haben (dh es sollte für AbstractVector funktionieren) und es wäre nützlich, die Erwartungen der Benutzer zu klären.

Ein großartiges Merkmal der aktuellen Destrukturierung ist, dass sie für alles ,

Um a, b... = c vorzustellen, müssen Sie Stellung dazu beziehen, wie c b . Z.B

  1. Zu sagen, dass nur c::Tuple erlaubt ist und b = Base.tail(c) eine Option ist, passt gut zu Typen, ist aber restriktiv, insbesondere im Hinblick auf den ursprünglichen Vorschlag.

  2. b zu collect(c)[2:end] ist eine weitere Option, aber es ist nicht schön für Benutzer, die Tupel destrukturieren wollen.

  3. Zu fragen, dass die Invariante (a, b...) == (c...,) (oder ähnlich) beibehalten wird und b jede iterierbare Variable sein darf, für die dies gilt, ist ebenfalls eine Option, die Tupel und alles, was iterierbar ist, aufnehmen könnte. Vielleicht könnte dies erreicht werden, indem diese Syntax auf eine Funktion reduziert wird, für die Benutzer Methoden definieren können (im Grunde f oben).

Vielleicht könnte dies dadurch erreicht werden, dass diese Syntax auf eine Funktion reduziert wird, für die Benutzer Methoden definieren können (im Wesentlichen f oben).

Ein guter Kandidat könnte peel , ein überladbares/nicht faules Äquivalent von Iterators.peel .

Ein guter Kandidat könnte Peel sein, ein überlastbares/nicht faules Äquivalent von Iterators.peel.

peel ist hier wahrscheinlich nicht die beste API, da wir nicht immer nur ein Element von vorne nehmen wollen. Ich denke, dass diese Funktion am freundlichsten zur konstanten Verbreitung ist, sollte diese Funktion wahrscheinlich einen Iterationszustand sowie die Anzahl der bereits verbrauchten Elemente akzeptieren, ähnlich wie iterate_and_index funktioniert. Ich habe dies in #37410 im Grunde als slurp_rest implementiert, nur mit der Ausnahme, dass es immer nur Tupel produziert.

Wäre es sinnvoll, diese Funktionalität als @slurp Makro in der nächsten Version einzuführen und abzuwarten, wie sie empfangen wird, bevor die neue Syntax hinzugefügt wird? Dies könnte viele nützliche Rückmeldungen von Benutzern bezüglich der sinnvollsten Semantik generieren, bevor sie offiziell in die Syntax aufgenommen wird, was später viel schwieriger zu ändern/veralten wäre.

Ja, und in einem Paket.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

JeffBezanson picture JeffBezanson  ·  145Kommentare

stevengj picture stevengj  ·  174Kommentare

quinnj picture quinnj  ·  179Kommentare

shelakel picture shelakel  ·  232Kommentare

StefanKarpinski picture StefanKarpinski  ·  138Kommentare