Julia: Überladen der ab-Feldzugriffssyntax zulassen

Erstellt am 10. Jan. 2013  ·  249Kommentare  ·  Quelle: JuliaLang/julia

decision parser

Hilfreichster Kommentar

Hier ist eine unterhaltsame 3-Zeilen-Implementierung:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Ich denke, dass a.b tatsächlich eine Projektionsfunktion Field{:b}() anstelle von getfield aufrufen sollte, damit Sie Funktionen wie x->x.a bereits kostenlos erhalten. Dadurch kann getfield auch immer einen Feldzugriff auf niedriger Ebene bedeuten.

Die obige Implementierung funktioniert vollständig, ist aber für den Compiler ziemlich schwierig (sysimg + 5%, was wirklich eine angenehme Überraschung ist). Dies erfordert also einige Spezialisierungsheuristiken und einige frühe Optimierungen müssen aktualisiert werden, aber dann wird dies hoffentlich realisierbar sein.

Alle 249 Kommentare

Die Fähigkeit, Punkte als syntaktischen Zucker für Mutator / Accessor-Methoden zu verwenden, wäre für viele Dinge gut. Ich habe dies immer in Sprachen geschätzt, die es bereitstellen, damit Sie Strukturfelder in kompliziertere Abstraktionen verwandeln können, ohne die API zu beschädigen.

+1

Ich habe eine absolut großartige Möglichkeit, dies umzusetzen.

Möchten Sie darüber sprechen? Ich weiß, dass Tom Short wirklich daran interessiert ist, dies für DataFrames zu haben, obwohl ich zunehmend skeptisch gegenüber der Weisheit bin, diese Funktion zu verwenden.

Dies würde das Aufrufen von Python-Code (über PyCall) erheblich angenehmer machen, da ich derzeit gezwungen bin, a[:b] anstelle von a.b zu tun.

@ JeffBezanson , gibt es eine Chance, dies für 0,3 zu haben? Wäre großartig für Inter-Language Interop, sowohl für PyCall als auch für JavaCall (cc @aviks).

@ JeffBezanson Wenn nicht, gibt es eine Chance, dass Sie eine Anweisung geben, wie Sie dies implementieren möchten? ( I have an absolutely awesome way to implement this. )

Nach meiner Erfahrung gibt es keinen schnelleren und sichereren Weg, Jeff dazu zu bringen, etwas zu implementieren, als eine Version davon zu implementieren, die er nicht mag ;-)

Die Grundidee ist, dass Sie implementieren

getfield(x::MyType, ::Field{:name}) = ...

damit Sie es pro Feld überladen können. Dies ermöglicht den Zugriff auf "echte" Felder, um weiterhin transparent zu arbeiten. Mit geeigneten Fallbacks funktioniert auch getfield(::MyType, ::Symbol) .

Das größte Problem ist, dass Module ein besonderes Verhalten in Bezug auf . . Theoretisch wäre dies nur eine andere Methode von getfield , aber das Problem ist, dass wir Modulreferenzen _earlier_ auflösen müssen, da sie sich im Grunde wie globale Variablen verhalten. Ich denke, wir müssen dies als Sonderfall für das Verhalten von . . Aufgrund der Analyse (# Typen) * (# Felder) zusätzlicher Funktionsdefinitionen besteht auch ein gewisses Problem mit der Effizienz des Compilers. Aber dafür werden wir nur sehen, was passiert.

@ JeffBezanson Beziehen Sie sich auch auf das Verhalten von const in Modulen? Es wäre nützlich, wenn ein Benutzertyp ein Modul emuliert und dem Compiler mitteilen kann, wann das Ergebnis einer dynamischen Feldsuche eine tatsächliche Konstante ist. (Ein anderer Ansatz wäre, mit einem tatsächlichen Modul zu beginnen und in der Lage zu sein, ein fehlgeschlagenes jl_get_global zu "fangen" und bei Bedarf neue Bindungen einzufügen.)

Ich würde das in Kombination mit # 5395 als sehr nützlich empfinden. Dann kann man einen Aufruf einer undefinierten Funktion oder Methode MyMod.newfunction(new signature) abfangen und bei Bedarf Bindungen an eine (möglicherweise große) API generieren. Dies würde dann wie üblich zwischengespeichert werden, denke ich.

Lassen Sie mich, eine einfache Julia-Neulingin, ein wenig Bedenken äußern: Ich denke, die Möglichkeit, den Punktoperator zu überladen, könnte bedeuten, dass die "Reinheit" des Feldzugriffs irgendwie verloren geht.

Der Benutzer würde im Allgemeinen das Wissen verlieren, wenn ab nur ein Zugriff auf eine Referenz / einen Wert ist oder wenn eine riesige Funktionsmaschinerie zurückgerufen werden kann. Ich bin mir nicht sicher, wie das schlimm sein könnte, es ist nur ein Gefühl ...

Andererseits sehe ich, dass dies in vielen Fällen (PyCall, Dataframes ...) ein großer Wunsch nach Syntaxzucker ist, der durchaus verständlich ist.
Vielleicht ist es Zeit für .. # 2614?

Ich unterstütze dies.

Aber die Reinheit hat etwas zu sagen, selbst wenn man mit names(Foo) herausfinden kann, was die wirklichen Komponenten von Foo sind.

Das Argument der Reinheit hängt eng mit dem praktischen Hauptanliegen zusammen, das ich habe, nämlich wie man mit Namenskonflikten umgeht, wenn die Felder des Typs Namen stören, die Sie möglicherweise verwenden möchten. Ich denke, wir würden dies in DataFrames beheben, indem wir die Verwendung von columns und colindex als Spaltennamen verbieten, wollten aber wissen, was die Leute dafür vorhatten.

Ich denke, getfield(x::MyType, ::Field{:foo}) = ... müsste verboten werden, wenn MyType ein Feld foo , sonst würde der Zugriff auf das reale Feld verloren gehen (oder eine Möglichkeit, den Zugriff auf das Feld zu erzwingen müsste verfügbar sein).
Aber dann konnte getfield nur für konkrete Typen definiert werden, da abstrakte Typen nichts über Felder wissen.

(In der Zwischenzeit bin ich auf C ++ gestoßen.)

Es ist kein großes Problem. Wir können so etwas wie Core.getfield(x, :f) bereitstellen, um den Zugriff auf die realen Felder zu erzwingen.

Ok, vielleicht bin ich verkauft. Aber dann ist es nett, eine Verknüpfung zu Core.getfield(x, :f) (z. B. x..f ), andernfalls muss der interne Code von Typen, die die . für alle Symbole (Datenrahmen, wahrscheinlich Wörterbücher) überladen, verwendet werden mit Core.getfield s überfüllt sein.

Ich mache mir keine Sorgen um den Reinheitsaspekt - bis wir diesen einzigen Code haben
Das sollte überhaupt Feldzugriff sein, ist Code, der zum gehört
Implementierung eines bestimmten Typs. Wenn der Feldzugriff Teil einer API ist, sind Sie
muss es dokumentieren, wie bei jeder api. Ich bin damit einverstanden, dass es nützlich sein könnte
Einige Verknüpfungssyntax für core.getfield, wenn Sie diese schreiben
Implementierungen.

Es wurde bereits in # 4935 darauf hingewiesen, aber lassen Sie es uns hierher ziehen: Die Überladung von Punkten kann sich ein wenig mit dem klassischen Mehrfachversand von Julian überschneiden, wenn sie nicht richtig verwendet wird, da wir damit beginnen können

getfield (x :: MyType, :: Field {: size}) = .........
für i = 1: y.size .....

Anstatt von

Größe (x :: MyType) = ..........
für i = 1: Größe (y) ....

Der Punkt eignet sich zwar hervorragend für den Zugriff auf Elemente in Sammlungen (Dataframes, Dicts, PyObjects), kann jedoch die Art und Weise ändern, in der auf Objekteigenschaften (keine Felder) zugegriffen wird.

Ich denke, eine Sache zu berücksichtigen ist, dass, wenn Sie Zugriff auf Feld überladen können, Sie auch in der Lage sein sollten, _setting_ ein Feld zu überladen. Sonst wird dies inkonsistent und frustrierend sein. Kannst du so weit gehen?

@nalimilan , man braucht unbedingt ein setfield! zusätzlich zu getfield . (Ähnlich wie setindex! vs. getindex für [] ). Ich denke nicht, dass dies umstritten ist.

Stimmen Sie mit setfield! für Spalten implementieren.

Ich unterstütze das.

Die Erfahrung mit anderen Sprachen (z. B. C # und Python) zeigt, dass die Punktsyntax einen großen praktischen Wert hat. Die Art und Weise, wie es mit speziellen Methoden implementiert wird, spricht weitgehend das Problem der Leistungsregression an.

Es ist jedoch wichtig sicherzustellen, dass die Inlinierbarkeit einer Methode durch diese Änderung nicht ernsthaft beeinträchtigt wird. Zum Beispiel wird so etwas wie f(x) = g(x.a) + h(x.b) nach dieser Landung nicht plötzlich uninlinierbar.

Wenn wir uns dazu entschließen, ist es nützlich, auch Makros bereitzustellen, um die Definition von Eigenschaften zu vereinfachen. Dies könnte folgendermaßen aussehen:

# let A be a type, and foo a property name
<strong i="10">@property</strong> (a::A).foo = begin
    # compute the return the property value
end

# for simpler cases, this can be simplified to
<strong i="11">@property</strong> (a::A).foo2 = (2 * a.foo)

# set property 
<strong i="12">@setproperty</strong> (a::A).foo v::V begin
    # codes for setting value v to a property a.foo
end

Hinter den Kulissen können all diese in die Methodendefinitionen übersetzt werden.

Ich bin nicht davon überzeugt, dass <strong i="5">@property</strong> (a::A).foo = viel einfacher ist als getproperty(a::A, ::Field{foo}) = ...

In jedem Fall kann ein besserer syntaktischer Zucker hinzugefügt werden, nachdem die Grundfunktionalität erreicht ist.

In Bezug auf Inlining sehe ich nicht, warum der Feldzugriff beeinflusst wird, solange entschieden wird, ob die umgebende Funktion inline geschaltet werden soll. Aber vielleicht ist dies nicht die Reihenfolge, in der das Inlining derzeit durchgeführt wird?

getproperty(a::A, ::Field{:foo}) = fällt mir auf, da es zu viele Doppelpunkte gibt :-) Ich stimme zu, dass dies eine Kleinigkeit ist, und wahrscheinlich müssen wir uns darüber jetzt keine Sorgen machen.

Ich mache mir Sorgen, ob dies zu einer Leistungsregression führen würde. Ich bin mir über den internen Code-Generierungsmechanismus nicht ganz klar. @ JeffBezanson kann wahrscheinlich etwas dazu sagen?

Der Feldzugriff ist sehr niedrig, daher werde ich dies nicht tun, ohne sicherzustellen, dass die Leistung erhalten bleibt.

Schließlich bin ich nicht davon überzeugt, dass das Überladen von Feldern eine gute Idee ist. Bei diesem Vorschlag gibt es immer zwei Möglichkeiten, eine Eigenschaft festzulegen: x.property = value und property!(x, value) . Wenn eine Feldüberladung implementiert ist, benötigen wir einen sehr starken Styleguide, um zu vermeiden, dass es zu einem totalen Durcheinander kommt, bei dem Sie nie im Voraus wissen, welche Lösung der Autor für einen bestimmten Typ ausgewählt hat.

Und dann wäre da die Frage, ob Felder öffentlich oder privat sind. Wenn keine Feldüberladung zulässig ist, wird das Typsystem klarer: Felder sind immer privat. Methoden wären öffentlich und Typen könnten deklarieren, dass sie Schnittstellen / Protokolle / Merkmale implementieren, dh dass sie einen bestimmten Satz von Methoden bereitstellen. Dies würde gegen @stevengjs https://github.com/JuliaLang/julia/issues/1974#issuecomment -12083268 zum Überladen von Feldern mit Methoden verstoßen, um das Brechen einer API zu vermeiden: Bieten Sie nur Methoden als Teil der API an und niemals Felder .

Der einzige Ort, an dem ich eine Feldüberladung bereuen würde, ist für DataFrames , da df[:a] nicht so schön ist wie df.a . Aber das hört sich nicht so an, als ob es allein eine so große Veränderung erfordern sollte. Der andere Anwendungsfall scheint PyCall zu sein, was möglicherweise darauf hinweist, dass eine Feldüberladung zulässig sein sollte, jedoch nur für hochspezifische, nicht julianische Anwendungsfälle. Aber wie kann verhindert werden, dass Benutzer eine Funktion missbrauchen, sobald sie verfügbar ist? In einem speziellen Modul verstecken?

@nalimilan , ich würde sagen, dass die Präferenz sein sollte, so viel wie möglich x.property Syntax zu verwenden. Die Sache ist, dass die Leute diese Syntax wirklich mögen - es ist sehr angenehm. Eine so nette Syntax zu nehmen und ausdrücklich zu sagen, dass sie immer nur für den internen Zugriff auf Objekte verwendet werden sollte, scheint geradezu pervers - "hah, diese nette Syntax existiert; benutze sie nicht!" Es erscheint viel vernünftiger, die Syntax für den Zugriff auf private Dinge weniger bequem und hübsch zu gestalten, anstatt APIs zu zwingen, die hässlichere Syntax zu verwenden. Vielleicht ist dies ein guter Anwendungsfall für den Operator .. : den privaten Operator _real_ field access.

Ich denke tatsächlich, dass diese Änderung die Dinge klarer und konsistenter machen kann als weniger. Betrachten Sie Bereiche - derzeit gibt es eine schreckliche Mischung aus step(r) und r.step Stilen. Besonders seit ich FloatRange habe, ist dies gefährlich, da nur Code korrekt ist, der step(r) . Der Grund für die Mischung ist, dass einige Eigenschaften von Bereichen gespeichert und andere berechnet werden - diese haben sich jedoch im Laufe der Zeit geändert und unterscheiden sich tatsächlich für verschiedene Arten von Bereichen. Es wäre besser, wenn jeder Zugriff den Stil step(r) Ausnahme der Definition von step(r) selbst. Aber es gibt einige steile psychologische Barrieren dagegen. Wenn wir r.step einem Methodenaufruf machen, der standardmäßig r..step , können die Leute einfach das tun, wozu sie natürlich geneigt sind.

Sollten wir, um Devil's Advocate (mit mir selbst) zu spielen, r.length oder length(r) schreiben? Inkonsistenzen zwischen generischen Funktionen und Methoden sind ein Problem, von dem Python betroffen ist, während Ruby sich voll und ganz dem r.length -Stil verschrieben hat.

+1 für .. als Core.getfield !

@StefanKarpinski Sinnvoll , aber dann müssen Sie die Syntax für private Felder hinzufügen, und die Schnittstellen müssen sowohl Methoden als auch öffentliche Felder angeben. In der Tat benötigen Sie einen Styleguide, um eine gewisse Konsistenz zu gewährleisten. Der Fall von length ist schwierig, aber es gibt auch zB size , der sehr ähnlich ist, aber einen Dimensionsindex benötigt. Diese Entscheidung öffnet eine Dose Würmer ...

In diesem Fall unterstütze ich auch .. für den Zugriff auf tatsächliche Felder und . für den Zugriff auf Felder, seien es Methoden oder reale Werte.

Sollten wir r.length oder length(r) schreiben, um Devil's Advocate (mit mir selbst) zu spielen? Inkonsistenzen zwischen generischen Funktionen und Methoden sind ein Problem, von dem Python betroffen ist, während Ruby sich voll und ganz dem r.length -Stil verschrieben hat.

Der Schlüsselfaktor, der für dieses Problem eindeutig sein kann, ist, ob Sie etwas als Funktion höherer Ordnung verwenden möchten oder nicht. Das heißt, die f in f(x) können Sie über eine Sammlung map , während die f in x.f nicht (kurz vor dem Schreiben) sind x -> x.f ) - Dies ist die gleiche Situation für alle Methoden in Einzelversandsprachen.

Warum beim Feldzugang anhalten? Was ist mit x.foo(args...) , das getfield(x::MyType, ::Field{:foo}, args...) = ... ? Dann könnten wir x.size(1) für die Größe entlang der ersten Dimension haben. (Ich bin mir nicht sicher, ob ich meinen Vorschlag mag, aber vielleicht etwas zu beachten. Oder wahrscheinlich auch nicht, da die Leute nur OO-ähnlichen Code schreiben?)

Das wäre mit dieser Funktionalität möglich. Welches ist eines der Dinge, die mich innehalten lassen. Ich habe kein Problem mit oo-Style-Code wie diesem - wie gesagt, es ist ziemlich angenehm und die Leute mögen es wirklich -, aber es bietet genügend Auswahlmöglichkeiten, um Dinge zu schreiben, für die wir wirklich eine strenge Richtlinie benötigen, was Sie tun sollten da Sie mit dem, was Sie tun können, sehr frei sein werden.

Als ich anfing, Julia zu lernen, half mir die No-Dot-Syntax sehr, den OO-Programmierstil mental loszulassen. Allein aus diesem Grund denke ich, dass mein Vorschlag schlecht ist.

Für eine einfache Überladung (dh nur a.b ohne (args...) ) stimme ich dem obigen Kommentar von . -Überladung verwenden, wird weniger deutlich, dass normale Felder nicht Teil der API sein sollten, und es wird wahrscheinlich empfohlen, Felder Teil der API zu machen.

Aber ja, die Syntax . ist praktisch ...

Wie wäre es mit: Die einzelne . sollte _nur_ syntaktischer Zucker für getfield(x::MyType, ::Field{:name}) = ... und der Feldzugriff erfolgt _nur_ nur durch .. (dh was . jetzt ist).

Dies würde eine klare Unterscheidung ermöglichen:

  • Das . ist für die öffentliche API vorgesehen, um auf wertähnliche Dinge von Typinstanzen zuzugreifen
  • Das .. ist für den Feldzugriff vorgesehen und sollte im Allgemeinen nicht in der öffentlichen API verwendet werden

Dies wäre natürlich eine bahnbrechende Veränderung.

Das ist im Grunde das, was ich vorgeschlagen habe, außer dass . standardmäßig .. damit es nicht kaputt geht.

Entschuldigung, ich hätte noch einmal lesen sollen!

Aber ich denke, dass das . nicht standardmäßig .. tatsächlich nett sein könnte (abgesehen davon, dass es kaputt geht), da es dem Entwickler eine Entscheidung darüber aufzwingen würde, was eine öffentliche API ist und was nicht. Wenn der Benutzer ein .. , kann er erwarten, dass sein Code möglicherweise beschädigt wird, während dies bei . nicht der

Das ist ein guter Punkt. Wir können diesen Weg gehen, indem wir a.b standardmäßig auf a..b mit einer Abwertungswarnung setzen.

Aus stilistischer Sicht würde ich es vorziehen, es zu sehen

a = [1:10]
a.length()
a.size()

als

a.length
a.size

Ich denke, es hilft, die Idee zu bewahren, dass eine Funktion aufgerufen wird, anstatt nur eine Eigenschaft abzurufen, die irgendwie im Typ gespeichert ist (zurück zu dem oben genannten Problem der "Reinheit"). Ich frage mich, ob es eine Möglichkeit gibt, diese Art von Stil sicherzustellen, damit die Dinge nicht so chaotisch werden wie in einigen anderen Sprachen.

Ich mag es nicht wirklich

a.length()

seitdem kann ich nicht sagen, ob es im ursprünglichen Typ ein Funktionsfeld gab. Wenn . niemals auf Felder zugreift, ist dies offensichtlich kein Problem. Ansonsten erscheint es mir verwirrend.

A priori denke ich, dass wir weder a.length() noch a.length . Aber die Frage ist warum? Was unterscheidet r.step von r.length ? Ist es anders Wenn sie nicht unterschiedlich sind, sollten wir step(r) und length(r) oder r.step und r.length ?

Mit der von Stefan vorgeschlagenen Semantik und der Hinzufügung von mir wäre klar, dass . immer ein Funktionsaufruf ist (genau wie + auch), während .. immer ein Feld ist Zugriff.

Zu der Frage, ob a.length usw. eine gute Idee ist: Wie wäre es mit . Zugriff sollte nur verwendet werden, um auf tatsächliche Daten des Typs zuzugreifen, mehr oder weniger so, als würde man die Einträge eines Diktats verwenden . Während wir uns an Funktionen für die Nicht-Daten-Eigenschaften wie size , length , step usw. halten, weil einige von ihnen zusätzliche Parameter und, glaube ich, die a.size(1) benötigen

Hier ist meine Meinung zu diesem Thema:

  • Die Punktsyntax sollte nur für Attribute eines Typs / einer Klasse verwendet werden. Bitte denken Sie daran, dass es nicht nur um Getter geht, sondern auch um Setter, und so etwas wie a.property() = ... fühlt sich völlig falsch an.
  • Während mir die aktuelle Situation gefällt, in der Funktionen die öffentliche API definieren und Felder privat sind, teile ich Stefans Meinung, dass die Punktsyntax zu schön ist, um für öffentliche APIs verboten zu werden. Aber bitte beschränken wir uns auf einfache Attribute. a.length ist ein gutes Beispiel, a.size(1) nicht, weil es ein zusätzliches Argument erfordert.
  • Bitte lassen Sie . standardmäßig .. . Julia ist nicht als Boilerplate-Sprache bekannt. Lass es uns so beibehalten

Bitte lassen Sie . standardmäßig .. . Julia ist nicht als Boilerplate-Sprache bekannt. Lass es uns so beibehalten

Ich stimme dem eher zu. Die Syntax zum Festlegen einer synthetischen Eigenschaft wäre nur a.property = b , nicht a.property() = b .

Klar, ich wollte nur klarstellen, warum a.property() als Syntax meiner Meinung nach nicht nett ist

Oder klarer: Das Wichtige an der Punktsyntax ist nicht, dass man Funktionen mit Typen / Klassen verknüpfen kann, sondern dass es möglich ist, Getter / Setter auf nette Weise zu schreiben. Getter / Setter sind wichtig für die Datenkapselung (halten Sie die Schnittstelle stabil, ändern Sie jedoch die Implementierung).

Diese Änderung wäre aus Sicht der API-Designer großartig, aber ich stimme zu, dass sie mit einer Art Styleguide geliefert werden sollte, um zukünftige Inkonsistenzen zu begrenzen.

Dies würde Ruby wie dsl's ...

amt = 1.dollar + 2.dollars + 3.dollars.20.cents 

Aber sei bereit für Java wie Wahnsinn:

object.propert1.property2.property3 ....

Nur ein paar Gedanken:

  • Ich möchte am liebsten die Syntax . für Dicts mit Symbolen als Schlüsseln. Es ist einfach schöner, d.key als d[:key] . Aber am Ende ist es nicht kritisch.
  • Ich denke, dass a->property besser liest als a..property . Aber auch hier ist es keine so große Sache und ich weiß nicht, ob es mit Julia-Syntax funktionieren würde.

@ BobPortmann Ich bin anderer Meinung. Ein Wörterbuch ist ein Containerobjekt, die API für Containerobjekte lautet obj [Index] oder obj [Schlüssel]. Da wir in Julia keine Eigenschaften haben, ist die Container-API derzeit überladen, um diese Funktionalität in Bibliotheken wie PyCall und OpenCL bereitzustellen. Diese Änderung trägt dazu bei, die Unterscheidung der Container-API zu stärken, da sie nicht überladen wird, um zusätzliche Funktionen bereitzustellen.

Die Verwendung von a->property für private Felder wäre ein guter Weg, um C-Hacker von Julia fernzuhalten ;-)

Ich mag die Syntax .. .

Für die Syntax a->property wird bereits gesprochen - das ist eine anonyme Funktion. Der a..b -Operator ist jedoch schon eine Weile zu gewinnen. In einigen Fällen möchten Sie etwas, das diktiert ist, aber viele optionale Felder enthält. Die Verwendung der Getter / Setter-Syntax dafür wäre besser als die Dikt-Indizierungssyntax.

"Für die a-> Eigenschaftssyntax wird bereits gesprochen - das ist eine anonyme Funktion."

Ja natürlich. Es sah nicht so aus, ohne Leerzeichen um -> .

Wie wäre es als Stilrichtlinie, wenn Sie empfehlen, die Eigenschaft (x) für schreibgeschützte Eigenschaften und die Eigenschaft x.property für Lese- / Schreibeigenschaften zu verwenden?

Für beschreibbare Eigenschaften ist x.foo = bar wirklich viel schöner als set_foo! (X, bar).

Es ist ziemlich verwirrend, foo(x) zum Lesen und x.foo zum Schreiben zu haben. Genau das machen Eigenschaften so attraktiv. Dieselbe Syntax für Lese- und Schreibzugriff, dh die einfachste Syntax, die man bekommen kann (für Getter und Setter)

In Bezug auf den Stil stellt sich die große Frage, ob wir sowohl x.length als auch length(x) haben möchten, wenn diese Funktion implementiert wird, oder ob das spätere Formular veraltet und entfernt werden sollte.

Meiner Meinung nach sollten wir nur eine Möglichkeit haben und in Zukunft nur noch x.length . Und was den Stil betrifft, denke ich, dass es ziemlich einfach ist. Alles, was eine einfache Eigenschaft eines Typs ist, sollte mithilfe der Feldsyntax implementiert werden. Alles andere mit Funktionen. Ich habe häufig Eigenschaften in C # verwendet und selten einen Fall gefunden, in dem ich mir nicht sicher war, ob etwas eine Eigenschaft sein sollte oder nicht.

Ich bin dagegen, einen zufällig ausgewählten Satz von 1-Argument-Funktionen in die Syntax x.f ändern. Ich denke, @ mauro3 hat einen guten Punkt gemacht, dass dies die Natur der Sprache verdunkelt.

a.b ist zumindest visuell eine Art Scoping-Konstrukt. Das b muss keine global sichtbare Kennung sein. Dies ist ein entscheidender Unterschied. Zum Beispiel haben Matrixfaktorisierungen mit einem oberen Teil eine .U -Eigenschaft, aber dies ist keine generische Sache - wir wollen keine globale Funktion U . Dies ist natürlich etwas subjektiv, zumal Sie U(x) = x.U leicht definieren können. Aber length ist etwas anderes. Es ist nützlicher, wenn es erstklassig ist (z. B. map(length, lst) ).

Hier sind die Richtlinien, die ich vorschlagen würde. Die Notation foo.bar ist geeignet, wenn:

  1. foo tatsächlich ein Feld mit dem Namen bar . Beispiel: (1:10).start .
  2. foo ist eine Instanz einer Gruppe verwandter Typen, von denen einige tatsächlich ein Feld mit dem Namen .bar . Selbst wenn foo kein bar -Feld hat, wird der Wert dieses Feldes durch seinen Typ impliziert. Beispiele: (1:10).step , (0.1:0.1:0.3).step .
  3. Obwohl foo bar nicht explizit speichert, speichert es äquivalente Informationen in einer kompakteren oder effizienteren Form, die weniger bequem zu verwenden ist. Beispiel: lufact(rand(5,5)).U .
  4. Sie emulieren eine API von einer anderen wie Python oder Java.

Es kann sinnvoll sein, die Eigenschaft bar in den Fällen 1 und 3 zuzuweisen, jedoch nicht in Fall 2. In Fall 2 können Sie die Eigenschaft bar nicht mutieren, da Sie den Typ eines Werts nicht ändern können das wird durch diesen Typ impliziert. In solchen Fällen möchten Sie wahrscheinlich die Mutation der bar -Eigenschaft der anderen verwandten Typen verbieten, indem Sie sie entweder unveränderlich machen oder foo.bar = baz explizit zu einem Fehler machen.

@tknopp , ich habe nicht vorgeschlagen, x.foo zum Schreiben und foo(x) zum Lesen zu verwenden. Mein Vorschlag war, dass, wenn eine Eigenschaft sowohl lesbar als auch beschreibbar ist, Sie sie wahrscheinlich mit x.foo lesen und schreiben möchten.

@StefanKarpinski : Aber ist length ein Fall von 3. Wo die Größen normalerweise gespeichert werden und length das Produkt der Größen ist?

Ich sehe Jeffs jedoch darauf hinweisen, dass diese Änderung diese Funktionen nicht mehr erstklassig machen würde.

@stevengj : Ich

@tknopp - Die Länge wird von den Größen abgeleitet, entspricht aber nicht diesen. Wenn Sie die Größen kennen, können Sie die Länge berechnen, aber nicht umgekehrt. Natürlich ist dies eine etwas verschwommene Linie. Der Hauptgrund, warum dies für lufact akzeptabel ist, ist, dass wir keine bessere API als diese gefunden haben. Ein anderer Ansatz wäre, generische Funktionen upper und lower zu definieren, die die oberen und unteren dreieckigen Teile allgemeiner Matrizen angeben. Dieser Ansatz lässt sich jedoch beispielsweise nicht auf QR-Faktorisierungen verallgemeinern.

Es ist bezeichnend, dass es nur wenige Fälle gibt, in denen _really_ nach dieser Syntax zu fragen scheint: Pycall, Faktorisierungen und möglicherweise Datenrahmen.
Ich mache mir große Sorgen, dass es zu einem zufälligen Durcheinander von f(x) vs. x.f . es würde das System viel schwieriger zu lernen machen.

Bedeutet Punkt 1 der Liste von @StefanKarpinski nicht, dass ein Feld eines Typs automatisch zur öffentlichen API gehört?

Im Moment kann ich sagen, was die öffentliche API eines Moduls ist: alle exportierten Funktionen und Typen (aber nicht ihre Felder). Nach dieser Änderung wäre es nicht möglich zu sagen, welche Felder zur öffentlichen API gehören sollen und welche nicht. Wir könnten anfangen, private Felder a._foo oder so zu benennen, wie in Python, aber das scheint nicht so schön zu sein.

Persönlich halte ich den DataFrames-Fall für etwas überflüssig. Wenn wir dies tun, werde ich die Funktionalität zu DataFrames hinzufügen, aber ich finde den Konsistenzverlust viel problematischer als das Speichern einiger Zeichen.

Ich würde die Entscheidung auch nicht von DataFrames, PyCall abhängig machen (und Gtk will es auch). Entweder wollen wir es, weil wir denken, dass Felder Teil einer öffentlichen Schnittstelle sein sollten (weil es "gut aussieht") oder wir wollen es nicht.

... pycall ...

und JavaCall

Da der Hauptanwendungsfall hierfür Interaktionen mit Nicht-Julia-Systemen zu sein scheint, können Sie den vorgeschlagenen Operator .. , anstatt . überladen.

Ich frage mich, ob eine einfachere Lösung hier ein allgemeinerer Tipp für OO ist:

#we already do
A[b] => getindex(A,b)
#we could have
A.b(args...) => b(A, args...)
# while
A..b => getfield(A,::Field{:b})
# with default
getfield(A, ::Field{:b}) = getfield(A, :b)

Es scheint, als würde dies JavaCall / PyCall erlauben, Methodendefinitionen "in" -Klassen durchzuführen, und gleichzeitig einen allgemeinen Stil zulassen, wenn Benutzer einen OO-Typcode wünschen, obwohl dieser sehr transparent ist. A.b() ist nur ein Umschreiben. Ich denke, das wäre sehr natürlich für Leute, die aus OO kommen.
Das neue getfield mit A..b zu haben, um dort eine Überladung zu ermöglichen, obwohl eine Überladung hier dringend empfohlen wird und nur für feldähnliche / Eigenschaften verwendet werden soll (ich vermute, dass es nicht sehr weit verbreitet sein würde aufgrund der leichten Knappheit der Überladung getfield(A, ::Field{:field}) .

@ Mauro3 :

Bedeutet Punkt 1 der Liste von @StefanKarpinski nicht, dass ein Feld eines Typs automatisch zur öffentlichen API gehört?

Das war eine Liste, wann es in Ordnung ist, die Notation foo.bar verwenden, nicht wenn es notwendig ist. Sie können die Notation foo.bar für "private" Felder deaktivieren, auf die dann nur über foo..bar .

@ Karbarcca : Mir ist nicht ganz klar, was Sie hier vorschlagen.

fwiw, ich bin ein Fan davon, den Ansatz zu akzeptieren, dass Erwachsene durch Konventionen zustimmen und . vollständig überladbar machen. Ich denke, der Doppelpunktvorschlag würde eher zu mehr Verwirrung als zu weniger führen.

@ihnorton - wie in Sie sind gegen die Verwendung von a..b als (nicht überladbare) Kernsyntax für den Feldzugriff oder gegen die Verwendung von a..b für die überladbare Syntax?

Eines der besten Merkmale von Julia ist seine Einfachheit. Das Überladen von x.y scheint der erste Schritt auf dem Weg zu C ++ zu sein.

@StefanKarpinski, aber dann würde dies einen ziemlichen Paradigmenwechsel von privaten Standardfeldern zu öffentlichen Standardfeldern bedeuten.

Eine Erkenntnis, die ich gerade hatte, wahrscheinlich war dies anderen die ganze Zeit klar. Eine vollständige Programmierung im OO-Stil kann mit dem grundlegenden . -Überladen durchgeführt werden (obwohl es hässlich ist). Definieren

getfield(x::MyType, ::Field{:foo}) = args -> foofun(x, args...) # a method, i.e. returns a function
getfield(x::MyType, ::Field{:bar}) = x..bar+2                  # field access, i.e. returns a value

dann funktionieren x.foo(a,b) und x.bar . Die Diskussion darüber, ob x.size(1) implementiert werden soll oder nur x.size ist also umstritten.

@StefanKarpinski gegen allgemein a..b und lauwarm über a..b -> Core.getfield(a,b) .

Ich sehe hier die Notwendigkeit eines anderen Operators, aber a..b ist nicht ganz überzeugend. Zwei Charaktere zu brauchen fühlt sich sehr ... zweitklassig an. Vielleicht a@b , a$b oder a|b (bitweise Operatoren werden einfach nicht so oft verwendet). Eine externe Möglichkeit ist auch a b`, die der Parser wahrscheinlich von Befehlen unterscheiden könnte.

Ich würde den "hässlichen" Operator für den primitiven Feldzugriff verwenden können. Ich denke, die Erfahrung hat gezeigt, dass es sich um eine konkrete Operation handelt, die nur selten verwendet wird und in der Tat etwas gefährlich ist.

Ich schlage vor, die Simulation des OO-Einzelversands durch die Konvention / das Umschreiben zuzulassen:

type Type end
# I can define methods with my Type as 1st argument
method(T, args...) = # method body
t = Type()
# then I can call that method, exactly like Java/Python methods, via:
t.method(args...)
# so
t.method(args...) 
# is just a rewrite to
method(t, args...)

Die Rechtfertigung hier ist, dass wir bereits ähnliche Syntaxumschreibungen für getindex / setindex! Durchführen. Lassen Sie uns also die vollständige OO-Syntax damit zulassen. Auf diese Weise müssen PyCall und JavaCall nicht

my_dna[:find]("ACT")
# they can do
my_dna.find("ACT")
# by defining the appropriate find( ::PyObject, args...) method when importing modules from Python/Java

Ich mag das, weil es eine ziemlich klare Transformation ist, genau wie getindex / setindex, aber auf Wunsch die Simulation eines einzelnen Versand-OO-Systems ermöglicht, insbesondere für OO-Sprachpakete.

Ich schlug dann die Verwendung des Operators .. für den Feldzugriff mit der Option zur Überladung vor. Die Verwendung hier würde es PyCall / JavaCall ermöglichen, den Feldzugriff zu simulieren, indem Aufrufe auf .. überladen werden, wodurch DataFrames .. für den Spaltenzugriff usw. überladen kann. Dies wäre auch der neue Standardfeldzugriff in allgemein für jeden Typ.

Ich habe eine Schwäche für reine Syntaxumschreibungen. Es ist wohl eine schlechte Sache, dass Sie jetzt a.f(x) schreiben können und es funktionieren lassen, aber etwas verwirrend anderes bedeuten als die meisten OO-Sprachen.

Natürlich ist die andere Seite dieser Medaille eine schreckliche Fragmentierung des Stils und die Tatsache, dass a.f nichts mit a.f() gemein hat, was dazu führt, dass die Illusion schnell zusammenbricht.

Eines der besten Merkmale von Julia ist seine Einfachheit. Das Überladen von x.y scheint der erste Schritt auf dem Weg zu C ++ zu sein.

Gleiches Gefühl hier. Ich überlegte, ob die tatsächliche Notwendigkeit dafür wirklich eine begrenzte Anzahl von Interop-Typen ist. Wie wäre es, wenn Sie sie nur dann gültig machen, wenn Sie in der Typdeklaration explizit dazu aufgefordert werden? Beispielsweise könnte ein zusätzliches Schlüsselwort neben type und immutable ootype oder so sein.

und die Tatsache, dass af nichts mit af () gemein hat, was dazu führt, dass die Illusion schnell zusammenbricht.

Können Sie klarstellen, was dies @JeffBezanson bedeutet?

Ich würde erwarten, dass a.f eine Art Methodenobjekt ist, wenn a.f() funktioniert.

Ah, verstanden. Ja, Sie wären definitiv nicht in der Lage, so etwas wie map(t.method,collection) zu tun.

Ich werde @ mauro3 zustimmen, dass durch das Zulassen von obj.method(...) Risiko besteht, dass neue Benutzer Julia nur als eine andere objektorientierte Sprache betrachten, die versucht, mit Python, Ruby usw. zu konkurrieren, und dies nicht vollständig zu schätzen wissen die Großartigkeit, die Mehrfachversand ist. Das andere Risiko besteht darin, dass der Standard-OO-Stil dann vorherrscht, da dies den Benutzern besser bekannt ist als der bisher entwickelte julianische Stil.

Da der Anwendungsfall außer DataFrames auf die Interaktion mit oo-Sprachen beschränkt ist, kann dies nur von Makros behandelt werden? dh aus <strong i="8">@oo</strong> obj.method(a) wird method(obj,a) ?

@karbarcca dies würde bedeuten, dass automatisch alles auf zwei Arten geschrieben werden könnte:

x = 3
x.sin()
sin(x)
x + 2
x.+(2) # ?!

@karbarcca https://github.com/JuliaLang/julia/issues/1974#issuecomment -38830330

t.method (args ...)
# ist nur ein Umschreiben an
Methode (t, args ...)

Für PyCall wäre dies nicht erforderlich, da der überladbare Punkt nur verwendet werden könnte, um pyobj[:func] von pyobj.func aufzurufen. Dann wäre pyobj.func() tatsächlich (pyobj.func)() .

Das Umschreiben von a.foo(x) als foo(a, x) würde das Problem für PyCall nicht lösen, da foo keine Julia-Methode ist und nicht sein kann. Ich muss sie zur Laufzeit dynamisch nachschlagen . Ich muss a.foo(x) als getfield(a, Field{:foo})(x) oder ähnliches umschreiben [oder möglicherweise als getfield(a, Field{:foo}, x) ], damit mein getfield{S}(::PyObject, ::Type{Field{S}}) das Richtige tun kann.

@ JeffBezanson https://github.com/JuliaLang/julia/issues/1974#issuecomment -38837755

Ich sehe hier die Notwendigkeit eines anderen Betreibers, aber a..b ist nicht ganz überzeugend. Zwei Charaktere zu brauchen fühlt sich sehr ... zweitklassig an

Ich würde sagen, dass .. andererseits viel schneller eingegeben wird als $ , @ oder | da keine Umschalttaste gedrückt werden muss und während er zwei Zeichen ist, bleibt der Finger auf derselben Taste: smile:

@stevengj Ah, ich

Für JavaCall benötige ich eigentlich nur einen unbekanntenProperty-Handler. Ich muss vorhandene Lese- oder Schreibfunktionen nicht neu schreiben oder abfangen. Würde eine Regel, dass "ax nur dann in getfield (a ,: x) umgeschrieben wird, wenn x keine vorhandene Eigenschaft ist", dazu beitragen, die Dinge gesund zu halten?

@simonbyrne , ein Makro erforderlich, würde den Wunsch nach sauberen und transparenten Interlanguage-Anrufen type Foo; p::PyObject; end und für ein Objekt f::Foo möchten Sie foo.p.bar wobei bar eine Python-Eigenschaftssuche ist. Es ist schwer vorstellbar, dass ein Makro die Bedeutung der beiden Punkte in foo.p.bar zuverlässig unterscheiden kann.

Ehrlich gesagt sehe ich die große Sache mit dem Stil nicht. Hochwertige Pakete imitieren den Stil von Base und anderen Paketen, wo dies möglich ist, und einige Leute schreiben seltsamen Code, egal was wir tun. Wenn wir in einem späteren Abschnitt des Handbuchs die Punktüberladung einfügen und die Verwendung nur in einigen sorgfältig ausgewählten Fällen empfehlen (z. B. Interoperabilität zwischen Sprachen, Lese- / Schreibeigenschaften, möglicherweise zur Vermeidung von Namespace-Verschmutzung für Dinge wie factor.U und im Allgemeinen als sauberere Alternative zu foo[:bar] ), dann denke ich nicht, dass wir mit Paketen überlaufen werden, die Punkt für alles verwenden. Die Hauptsache ist zu entscheiden, wofür _we_ dies verwendet und empfiehlt, und wahrscheinlich sollten wir die Liste der empfohlenen Verwendungen sehr kurz halten und sie nur erweitern, wenn sich reale Anforderungen ergeben.

Wir fügen keine supereinfache OO-ähnliche Syntax wie type Foo; bar(...) = ....; end für foo.bar(...) , sodass die Versuchung auch für Neulinge begrenzt wird.

Grundsätzlich stimme ich ganz zu . Ich mag a..b für echten Feldzugriff, weil es

  1. sieht ähnlich aus wie a.b
  2. ist weniger bequem, wie es sein sollte
  3. ist nur leicht weniger bequem
  4. hat keine existierende Bedeutung und wir haben seit über einem Jahr keine überzeugende Verwendung dafür gefunden
  5. ist nicht schrecklich komisch wie a b`

Mit dieser Änderung und möglicherweise (https://github.com/JuliaLang/julia/issues/2403) wird fast die gesamte Syntax von Julia überladbar sein? (Der ternäre Operator ist die einzige Ausnahme, an die ich denken kann.) Dass fast die gesamte Syntax auf überladbaren Methodenversand reduziert ist, scheint mir ein stark einheitliches Merkmal zu sein.

Ich bin damit einverstanden, dass es tatsächlich eine Art Vereinfachung ist. Der ternäre Operator und && und || sind wirklich Kontrollabläufe, das ist also etwas anders. Natürlich spricht diese Art von Argumenten dagegen, a..b zum echten Feldzugriff zu machen, seitdem _das_ die einzige nicht überladbare Syntax wäre. Aber ich denke immer noch, dass es eine gute Idee ist. Konsistenz ist gut, aber nicht um ihrer selbst willen von größter Bedeutung.

Oh, es gibt auch einen Funktionsaufruf, der nicht überladbar ist. So einfach, dass ich es vergessen habe.

Das ist, was Problem # 2403 adressiert.

Ja. Aber das ist viel näher als das.

Der einzige Vorteil für mich ist, dass es wirklich schön wäre, den echten Feldzugriffsoperator für Module zu verwenden, aber das wird wahrscheinlich nicht passieren, da niemand Package..foo schreiben möchte.

Das Ausfüllen von Registerkarten nach Punkten wird etwas hässlich. Technisch müssen Sie überprüfen, welche Methode x. aufrufen kann, um festzustellen, ob es angemessen ist, Objektfeldnamen oder Modulnamen aufzulisten. Und ich hoffe, niemand versucht, getfield(::Module, ...) zu definieren.

Ich denke, dass das Ausfüllen von Registerkarten folgendermaßen erfolgen kann: foo.<tab> listet die "öffentlichen Felder" auf und foo..<tab> listet die "privaten Felder" auf. Wäre es für Module in Ordnung, nur die Standardimplementierung von Mod.foo Mod..foo zuzulassen und den Leuten nur zu sagen, dass sie Module keine getfield-Methoden hinzufügen sollen? Ich meine, Sie können die Ganzzahladdition in der Sprache bereits neu definieren - die Hölle bricht los und Sie erhalten einen Segfault, aber wir versuchen nicht, dies zu verhindern. Das kann doch nicht schlimmer sein, oder?

Es ist in der Tat etwas schlimmer, weil sich eine Programmiersprache wirklich nur um die Benennung kümmert. Das Auflösen von Namen ist viel wichtiger als das Hinzufügen von Ganzzahlen.

Wir haben keine andere Wahl, als Mod.foo standardmäßig auf Mod..foo , aber wir müssen wahrscheinlich an einigen Stellen Mod..foo für das Bootstrapping verwenden. Der Operator .. ist hier äußerst hilfreich, da Sie ohne ihn nicht einmal Core.getfield aufrufen können, um den Fallback zu definieren. Damit würden wir wahrscheinlich nur Core.getfield entfernen und nur .. .

Das ist ein fairer Punkt - das Benennen ist eine große Sache in der Programmierung :-). Scheint ein guter Weg zu sein - nur .. und kein Core.getfield .

Diese beiden Ideen,

[...] stellen Sie die Punktüberladung in einen späteren Abschnitt des Handbuchs und empfehlen Sie die Verwendung nur in einigen sorgfältig ausgewählten Fällen @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -38847340

und

[...] sollte bevorzugt werden, die x.property-Syntax so weit wie möglich zu verwenden @StefanKarpinski https://github.com/JuliaLang/julia/issues/1974#issuecomment -38694885

sind klar dagegen.

Ich denke, wenn die erste Idee ausgewählt werden soll, ist es sinnvoller, nur einen neuen .. -Operator für diese "sorgfältig ausgewählten Fälle" zu erstellen.
Als Vorteil wäre die Verwendung von ..name für Fälle, in denen derzeit [:name] verwendet wird (DataFrames, Dict {Symbol, ...}), typ- / syntaxfreundlicher, während klar angegeben wird, dass sich etwas vom Feldzugriff unterscheidet passierte. Darüber hinaus könnte der doppelte Punkt in ..name als gedrehter Doppelpunkt angesehen werden, ein Hinweis auf die Symbolsyntax :name , und es würde auch kein Problem mit Tab-Vervollständigungen geben.
Als Nachteil haben die Verwendungen in PyCall et al. wäre nicht so nah an den ursprünglichen Syntaxen (und könnte sogar verwirrend sein für die Fälle, in denen die . wirklich verwendet werden müssen). Aber seien wir ehrlich, Julia wird niemals vollständig mit der Python-Syntax kompatibel sein, und es wird immer Fälle geben, in denen man mit PyCall viel in Julia eingeben muss, um ansonsten einfache Anweisungen in Python auszuführen. Die .. , um . .. zu emulieren, könnten hier ein gutes Gleichgewicht ergeben. (Bitte versteh mich nicht falsch, ich mag PyCall wirklich und denke, es ist eine wichtige Funktion, die besondere Sorgfalt verdient.)

Die zweite Idee, die ich derzeit bevorzuge, hat die große Entscheidung, wann property(x) oder x.property verwendet werden müssen, was eine elegante, durchdachte und klare Definition erfordert, falls es so etwas gibt. .
Es scheint, dass wenn Leute einen überladbaren . , das daran liegt, dass sie in erster Linie den API-Stil von x.property bevorzugen.
Wie auch immer, ich würde es vorziehen, . nicht als überladbaren Feldzugriffsoperator zu sehen, sondern als überladbaren "Eigenschafts" -Zugriffsoperator ( getprop(a, Field{:foo}) vielleicht?), Der standardmäßig einen nicht überladbaren Feldoperator .. .
Es müssten auch andere Entscheidungen getroffen werden, z. B. welche im konkreten Implementierungscode für den Feldzugriff verwendet werden, .. oder . ? Was ist zum Beispiel für das Schrittbeispiel "Bereiche" idiomatisch? step(r::Range1) = one(r..start) oder step(r::Range1) = one(r.start) ? (ganz zu schweigen von der Frage, ob step eine Methode oder eine Eigenschaft sein muss).

Aus diesem Grund habe ich mich aus diesem Blickwinkel zurückgezogen und folgende Kriterien vorgeschlagen: https://github.com/JuliaLang/julia/issues/1974#issuecomment -38812139.

Nur ein Gedanke, der mir beim Lesen dieses interessanten Threads in den Sinn kam. Der Export kann verwendet werden, um öffentliche Felder zu deklarieren, während alle Felder innerhalb des definierenden Moduls sichtbar sind, z.

module Foo
   type Person
     name
     age
   end
   export Person, Person.name
   <strong i="6">@property</strong> Person :age(person) = person..age + 1
end

In dieser Situation sieht die exportierte Person immer noch wie 'Name' und 'Alter' aus, außer in diesem Fall wird das Alter nur durch eine Funktion gelesen, die eine hinzufügt. Das Exportieren der gesamten Person kann als Exportperson erfolgen. * Oder ähnlich.

[pao: Zitate]

@emeseles Bitte verwenden Sie Backticks, um Dinge zu zitieren, die Julia-Code ähneln. Dies stellt sicher, dass die Formatierung beibehalten wird, und verhindert, dass Julias Makros GitHub-Benachrichtigungen für Benutzer mit ähnlichen Namen erstellen.

. und .. sind verwirrend: Eine klare und leicht zu merkende Sintax ist etwas Gutes

Ich freue mich sehr darauf, dies tun zu können. Ist dies eine ausreichend große Änderung, um sie (oder den WIP in # 5848) als 0,4-Projekt zu kennzeichnen?

Ja, es ist definitiv ein Projekt.

Ich denke, die meisten von uns sind sich einig, dass die empfohlenen Verwendungszwecke zumindest zu Beginn begrenzt sein sollten. Meiner Meinung nach sollte es zunächst nur für zwei Verwendungszwecke empfohlen werden: Interoperabilität (mit anderen Sprachen wie in PyCall und allgemeiner für externe Bibliotheken, in denen die Punktnotation natürlich ist) und möglicherweise für Objekte mit veränderlichem Status (seit get_foo(x) und set_foo!(x, val) sind hässlich).

Selbst wenn wir es nur für die Interoperabilität von Auslandsanrufen empfehlen, reicht dieser Zweck meiner Meinung nach aus, um diese Funktion zu rechtfertigen. Für eine neue Sprache wie Julia ist es äußerst wichtig, reibungslos mit dem Rest des Softwareuniversums zu sprechen.

Steven, ich bin mir über den Getter / Setter nicht 100% sicher, weil ich befürchte, dass dies bald zu Inkonsistenzen führen wird, aber ich stimme dem anderen Anwendungsfall zu. Darüber hinaus haben wir in Gtk.jl dynamische Eigenschaften, die auch von der Syntax profitieren würden. Mein persönlicher Favorit ist die Enum-Implementierung, die Stefan in # 5842 skizziert hat.

Stoßen. Was blockiert den Fortschritt in diesem Bereich? Ist eine Entscheidung erforderlich oder hängt dieses Problem von anderen internen Änderungen ab, die noch nicht vorgenommen wurden, oder handelt es sich nur um eine Codierung?

Was blockiert den Fortschritt in diesem Bereich?

Jemand, der die Arbeit macht und zögert, ob es das Richtige ist.

Beachten Sie, dass @ihnorton bereits unter # 5848 einen frühen Implementierungsentwurf erstellt hat. Ich denke, die Arbeit ist in erster Linie ins Stocken geraten, weil eine klare Aussage des Julia-Kernteams erforderlich ist, ob dies eine gewünschte Funktion ist.

Ich bin damit an Bord. @ JeffBezanson scheint auf dem Zaun zu sein.

Mit dieser Funktion würde mir der Übergang von unserer großen Python-Codebasis zu Julia erleichtert. Wenn Schüler Python-Code verwenden und eine ganz andere Syntax benötigen als sie es gewohnt sind, kann dies schwierig werden.

Wir hatten diese Diskussion oben in diesem Thread und ich kann immer noch keine vollständige Übereinstimmung sehen. Derzeit denken mehrere Leute, dass eine öffentliche API aus Funktionen / Methoden besteht, während die private API die Felder eines zusammengesetzten Typs sind. Ich kann sehr seltene Ausnahmen von diesem Schema sehen. ( .U in einer LU-Zerlegung?)

Dies bedeutet nicht, dass ich dagegen bin, da Python-Zugriff und Aufzählungen Fälle sind, in denen dies nützlich ist. Dennoch kann man sich fragen, wie dringend die Notwendigkeit hier ist und ob es sinnvoll wäre, dies am Ende eines Entwicklungszyklus voranzutreiben.

@ ufechner7 , ich stimme zu, dass die Hauptmotivation das Interop zwischen den Sprachen ist. @tknopp , wir werden uns über so etwas nie einstimmig einigen. Letztendlich kommt es darauf an, was @JeffBezanson und @StefanKarpinski entscheiden.

Ich denke, ein Großteil des Zögerns rührt von dem her, was ich mir als Jeffs schlimmsten Albtraum vorstelle:

module DotOrientedProgramming
  Base.getfield(x, ::Field{:size}) = size(x)
  Base.getfield(x, ::Field{:length}) = length(x)
  ⋮
end

Ich würde das auch sehr ablehnen - jedes Paket, das beschließt, es so zu missbrauchen, wird allen Typen im System, einschließlich meiner eigenen, ihren Missbrauch auferlegen. Diese Funktion ist sehr leistungsfähig und wird die Schreibweise von Julia ändern. Zum Guten und (vielleicht, aber hoffentlich nicht) zum Schlechten.

Ja sicher Steven, das ist vielleicht nicht richtig von mir formuliert. Der Punkt, den ich ansprechen wollte, ist, dass diese Änderung einen großen Einfluss darauf haben kann, wie sich die Sprache entwickeln wird. Und die Idee der "formalen Schnittstelle", die wir in einem anderen Problem haben, wird auch dadurch beeinflusst, dass . überladbar wird. Also ja, lassen Sie @JeffBezanson und @StefanKarpinski entscheiden. Die Frage ist immer noch, ob die Entscheidung jetzt durchgesetzt werden muss ...

Für das, was es wert ist, habe ich es vorgezogen, fast die gesamte Syntax überladbar zu machen und mich dann auf die Kultur zu verlassen, um nicht zu wild damit zu werden.

+1 . Ich denke, es gibt hier ein starkes philosophisches (und möglicherweise praktisches ...) Analogon zur Überladung von call . Das Handbuch benötigt einen Abschnitt mit dem Titel Don't do stupid stuff: we won't optimize that . (Natürlich war das Überladen von call teilweise aus Leistungsgründen - aber es ist voller Missbrauchspotenzial)

  • : 100: dazu. Ich denke, Kultur sollte genug Motivation sein, um dies nicht zu missbrauchen

Insgesamt bin ich dafür. Missbrauchspotential ist nicht meine größte Sorge. Für mich sind die großen Probleme

  • Akzeptable Syntax für "echten" Feldzugriff. Ich mag a..b so sehr.
  • Module. Qualifizierte Namen sind äußerst wichtig. Das Ausdrücken mit Methodenversand ist möglich, hat jedoch praktische Schwierigkeiten. Sie müssen nämlich viele Compiler-Phasen durchlaufen (möglicherweise sogar durch Inlining), um zu wissen, dass Sie einen qualifizierten Namen haben. Dies macht das Leben für jeden, der Tools schreibt, die ASTs verbrauchen, schwieriger. Es macht es auch sehr einfach, diesen Fall in solchen Werkzeugen falsch zu behandeln.

Diese Probleme könnten auf einen Schlag gelöst werden, indem für beide dieselbe Syntax verwendet wird. Es ist jedoch fast unmöglich, sich vorzustellen, dass zu diesem Zeitpunkt für Module nur . wird. _Internally_ wird es definitiv eine abstrakte Syntax für Modulreferenzen geben; Es wäre frustrierend, wenn es keinen guten Weg gäbe, das aufzudecken.

Meine zwei Cent zu dieser Frage: Warum nicht : für qualifizierte Namen verwenden? Es wird bereits für etwas Ähnliches verwendet:

import Base: call, show, size

Dies würde so etwas geben

module Foo
    module Bar
        f(x) = 3*x
    end
    const a = 42
end

<strong i="10">@assert</strong> Foo:a == 42

Foo:Bar:f(789)

Oder wird das Symbol : bereits zu häufig verwendet? Das Symbol :: (C ++ - Stil) scheint mir zu ausführlich zu sein.

Das : ist bereits das am meisten überladene Symbol in Julia, also wird das leider nicht helfen.

Können wir das Problem der qualifizierten Benennung vereinfachen, indem wir module.name nicht überladbar machen? Da die Modulbindungen konstant sind, können wir die gleiche Semantik beibehalten, aber die gesamte normale Logik für die Suche nach qualifizierten Namen kurzschließen, sobald bekannt ist, dass die LHS von a.b ein Modul ist. Ich denke, es ist ziemlich vernünftig, nicht zuzulassen, dass Leute überschreiben, was es bedeutet, einen Namen in einem Modul nachzuschlagen.

Ich mag eher die a..b -Syntax für echten Feldzugriff. Was ist Ihr Einwand dagegen?

Nebenbei: Ich wünschte, wir hätten ( ) für Importlisten wie einige der funktionalen Sprachen verwendet. Dh:

import Base (call, show, size)

Mein Grund ist, dass wir die Kommas optional machen und nachfolgende Kommas zulassen könnten. Es ärgert mich wirklich, dass alle importierten Namen nachgestellte Kommas benötigen, mit Ausnahme des letzten, der keinen haben kann.

Ja, ich wollte gerade die Möglichkeit erwähnen, a.b bedeuten zu lassen "Wenn a ein Modul ist, dann mache zuerst eine Modulsuche". Das könnte helfen, und wir möchten die Bedeutung der Modul-Suche auf keinen Fall außer Kraft setzen. Es hat jedoch einige Komplexitätskosten, da wir dann a.b als Aufruf getfield(a,:b) . Es muss ein spezieller AST-Knoten mit einer impliziten Verzweigung sein. Natürlich könnten wir einen expliziten Zweig verwenden, aber ich würde mir Sorgen machen, dass AST dadurch aufgebläht wird.

Es scheint keinen einfachen Ausweg aus einem so großen Konflikt zwischen den Bedürfnissen des Frontends und des Backends zu geben.

Wenn alle anderen a..b mögen, kann ich wohl lernen, damit zu leben. Es sieht für mich nur so aus, als würde es etwas völlig anderes bedeuten, vielleicht eine Pause.

Ich mag a..b auch nicht, frage mich aber, warum es überhaupt erforderlich wäre. Beim Lesen dieses Threads entsteht der Eindruck, dass Überladung nur in Sprachumschlägen und dynamischen Anwendungsfällen verwendet wird, in denen kein realer Feldzugriff erforderlich ist.

Denn irgendwann müssen Sie auf die Darstellung eines Objekts zugreifen, um etwas damit zu tun. Man könnte argumentieren, dass dies relativ selten wäre und daher hässlich sein könnte wie get_actual_field(a,:x) , aber dies scheint eine zu wichtige Operation zu sein, um keine Syntax zu haben.

Ich sehe das, aber das klingt so, als würden wir nach einer Syntax suchen, die niemand verwenden soll, oder?

Wenn Sie .. nicht angeben, können Sie für dynamische Anwendungsfälle Ja sagen, für punktorientierte Programmierung jedoch Nein

Ich sehe nicht ein, wie dies eine punktorientierte Programmierung verhindern würde. Sie könnten immer noch das obige Beispiel von

Während die a..b -Syntax wie ein Intervall aussieht (ich habe sie als solches verwendet), glaube ich nicht, dass die Intervallarithmetik eine eigene Eingabesyntax benötigt - das Schreiben von Interval(a,b) ist gerecht gut und es gibt nicht viel anderes, für das jemand diese Syntax verwenden möchte, da es seit Jahren ein Operator in Julia ist und niemand es für viel von irgendetwas verwendet. Es sieht auch nach Feldzugang aus.

Ein Silberstreifen dazu ist, dass wir die abscheulichen module_name durch m..name ersetzen können. Es war eine Warze, nicht auf die Felder von Modulobjekten zugreifen zu können.

Ja, ich wollte gerade die Möglichkeit erwähnen, a.b bedeuten zu lassen "Wenn a ein Modul ist, dann mache zuerst eine Modulsuche". Das könnte helfen, und wir möchten die Bedeutung der Modul-Suche auf keinen Fall außer Kraft setzen. Es hat jedoch einige Komplexitätskosten, da wir dann a.b als Aufruf getfield(a,:b) . Es muss ein spezieller AST-Knoten mit einer impliziten Verzweigung sein. Natürlich könnten wir einen expliziten Zweig verwenden, aber ich würde mir Sorgen machen, dass AST dadurch aufgebläht wird.

Können wir damit umgehen , indem sie a.b bedingungslos mittlere getfield(a,:b) hinzufügt es einen Fehler zu machen und dann Methoden getfield der verstrickten getfield(::Module, ::Field) Methode? Es ist eine seltsame Art, dieses Verhalten durchzusetzen, aber es würde letztendlich den gleichen Effekt haben. Dann könnte das Verringern nur die Tatsache nutzen, dass wir wissen, dass Sie das nicht tun können, um zu betrügen und module.name auf die Suche nach qualifizierten Namen zu senken.

Ok, ich sage es umgekehrt: Würde jemand in diesem Thread .. und wenn ja, was wäre ein beispielhafter Anwendungsfall? (dh möglicherweise ist das vollständige Abschatten des internen Feldzugriffs in Ordnung)

@StefanKarpinski Ja, das könnte funktionieren. Könnte ein anderer Fall sein, in dem wir eine Art "versiegelte" Methoden wollen.

@tknopp Zugriff auf module..name und module..parent :) Zur Verdeutlichung befürworten Sie auch eine Funktionsaufrufsyntax wie get(obj,:field) für den Feldzugriff auf niedriger Ebene?

Nein, ich befürworte keine bestimmte Syntax. Ich denke nur, es wäre gut sicherzustellen, warum diese Funktion benötigt wird und welche Anwendungsfälle es gibt. Für die dynamischen Anwendungsfälle wäre das in Ordnung

  • a.b ist ein Feldzugriff, wenn Base.getfield(a, ::Field{:b}) nicht definiert wurde
  • a.b ist die dynamische Version, wenn Base.getfield(a, ::Field{:b}) definiert ist. In diesem Fall könnte der reale Feldzugriff beschattet werden

Meine Frage war, ob es Anwendungsfälle gibt, in denen das Abschatten nicht in Ordnung ist.

Ja; Vielleicht möchten Sie pyobject.x so definieren, dass x immer im Wörterbuch des Pyobjekts für alle x nachgeschlagen wird. Dann wird ein separater Mechanismus benötigt, um auf die Julia-Felder des Pyobjekts zuzugreifen.

Ahhh, also ist alles oder nichts? Ich hatte irgendwie den Eindruck, dass man haben könnte

type A
  c
end

Base.getfield(a::A, ::Field{:b}) = 3

a = A(1)

a.c # This still calls the field access
a.b # This calls the function

Ja, Sie können das tun, aber nicht alle Objekte werden es tun. Einige möchten getfield(a::A, ::Field) , um alle Felder abzufangen.

Ok, danke, jetzt verstehe ich es. Alle dynamischen Anwendungsfälle möchten getfield(a::A, ::Field) und benötigen daher eine Möglichkeit, die internen Felder aufzurufen.

Dann ist meine Meinung, dass Core.getfield ausreicht, es sei denn, jemand findet einen praktischen Anwendungsfall, bei dem dies ärgerlich ist.

Dies ist wahrscheinlich eine Selbstverständlichkeit, aber wir werden auch das Überschreiben von setfield! zulassen, oder? Ich würde das wirklich mögen, um veränderbare Ansichten in einer Datenbank verfügbar zu machen, in der Zeilen zu Typen werden.

Ja, das war mein Eindruck.

Ok, IMHO ist es keine so große Sache, ob .. für den echten Feldzugriff oder Core.getfield soll. Man könnte das allgemeine Merkmal als experimentell einführen und dieses Thema ändern.

Die Frage ist, ob dies in den Zeitrahmen von 0,4 passt oder nicht. Also ist es # 5848 kurz vor der endgültigen Implementierung und das Modul lösbar?

@johnmyleswhite : Ich würde auch dafür stimmen, dass dies symmetrisch ist und setfield! erlaubt. In Gtk.jl würden wir beide verwenden.

Es scheint nicht sehr klar zu sein, was die Regel wäre, wann diese Funktion verwendet werden soll und wann nicht. Ich sehe den Punkt für PyCall, an dem eine Methode / ein Feld dynamisch nachgeschlagen werden muss und daher keine Julia-Methode / ein zusammengesetzter Typ sein kann (und die resultierende Syntax näher an Python liegt). Aber warum dann für Gtk.jl verwenden? Wenn es anfängt, foo.bar = x anstelle von setbar!(foo, x) auszuführen, wird der Standard-Julia-Code auch dieses Muster unweigerlich verwenden: Ist es das, was wir wollen? Vielleicht ist es das, aber lassen Sie uns das klarstellen.

Wäre es akzeptabel / empfohlen, diese Funktion zu verwenden, um Eigenschafts-Getter und -Setter zu implementieren, die für abstrakte (und auch konkrete) Typen definiert sind?
Ich denke, dies würde die Vermeidung von Namenskonflikten bei Methoden ermöglichen, mit denen Eigenschaften von verschiedenen Typen verschiedener Module abgerufen werden.

Ref.: Https://github.com/JuliaLang/julia/issues/4345 , https://groups.google.com/forum/#!msg/julia -users / p5-lVNlDC8U / 6PYcvvsg29UJ

@nalimilan : Gtk hat ein dynamisches Eigenschaftssystem, bei dem es nicht um Getter / Setter geht.

@tknopp Ah, OK, in der Tat. Für die meisten gängigen Eigenschaften haben Sie jedoch eine (schnelle) Getter / Setter-Funktion sowie die dynamische Eigenschaft. Würden Sie also empfehlen, die Getter / Setter-Funktion zu verwenden, sofern verfügbar, und die Syntax zum Überladen von Feldern nur für Eigenschaften, die keine haben? Klingt gut für mich - aber es ist gut, eine klare Richtlinie zu diesem IMHO zu haben.

Meiner Ansicht nach (und ich denke, wir müssen ein wenig damit experimentieren, um die richtigen Regeln herauszufinden) ist f(x) besser, wenn f als allgemeines eigenständiges Konzept wie " Länge "während x.f verwendet werden sollte, wenn f nicht wirklich unabhängig von x . Um zu versuchen, mein vorheriges Beispiel in dieses zu integrieren, ist es nicht wirklich nützlich, eine generische step -Funktion zu haben, da die meisten Vektoren und Sammlungen keine Vorstellung von Schritt haben - es ist nur dann sinnvoll, wenn Sie einen Bereich haben irgendeiner Art. Daher ist es in Ordnung, wenn x.step der Weg ist, um den Schritt eines Bereichs x . Es ist ein bisschen wie ein Urteilsspruch, aber ich denke, das Leben ist voll davon.

Ich mag .. nicht, da es keinen direkten Zugriff auf mich vermittelt. Wie wäre es mit foo.bar. Der zusätzliche Punkt am Ende fixiert ihn als direkten Zugriff.

Könnte auch ein Unicode-Symbol auswählen: Wir haben noch viele davon übrig ...

@GlenHertz , das funktioniert nicht wirklich, wenn Sie

@simonbyrne , ich bin generell dagegen, irgendetwas in der Kernsprache oder Standardbibliothek zu haben, das die Verwendung von Unicode erfordert. Es zuzulassen ist eine Sache, Menschen zu zwingen, es zu benutzen, ist eine ganz andere.

Meiner Ansicht nach (und ich denke, wir müssen ein wenig damit experimentieren, um die richtigen Regeln herauszufinden) ist f (x) besser, wenn f als allgemeines eigenständiges Konzept wie "Länge" sinnvoll ist, während xf verwendet werden sollte wenn f nicht wirklich unabhängig von x ist.

Meine persönliche Regel für die Verwendung dieser Funktion lautet: Verwenden Sie diese Funktion nur für Sprachinterop oder für Dinge, die entweder "fast Felder" oder "erweiterte Felder" sind. Zum Beispiel könnte ich dies verwenden, um einen Cache zu aktualisieren, der vom Wert aller Felder in einem Typ abhängt.

Eine große Frage, die ich zu dieser Funktion habe: Wie interagiert dies mit der Typinferenz? Es scheint, als würden Sie eine Funktion getfield(x::T, s::Symbol) , die eine unterschiedlich typisierte Ausgabe für unterschiedliche Werte von s . Funktioniert das nur, weil getfield magisch ist? Können Sie die Ausgabe von getfield(x, s) für feste x und s zu jedem Zeitpunkt in einem Programm neu definieren? Wenn ja, wie hängt das mit der Unfähigkeit zusammen, einen Typ neu zu definieren?

Es scheint, als würden Sie eine Funktion getfield(x::T, s::Symbol) , die eine unterschiedlich typisierte Ausgabe für unterschiedliche Werte von s .

Aus diesem Grund ist geplant, dies als getfield{s}(x::T, f::Field{s}) auszudrücken, wobei s ein Symbol ist.

Das hatte ich vermisst. Danke, dass du mich klargestellt hast.

@nalimilan : Ja, die überladenen Felder werden nur für die dynamischen Eigenschaften verwendet. So will Jameson das angehen und ich finde das gut. Alle echten Getter und Setter werden automatisch generiert, funktionieren aber immer noch ohne alle get / set-Namen. Das Live im GAccessor Modul (kurz G_ )

Verwenden Sie in der Syntax <- für den echten Feldzugriff.
Es ähnelt -> in c ++, das für Lamdas verwendet wird, aber <- wird derzeit nicht verwendet.
Es könnte ab dem Typ gelesen werden, den Wert direkt abrufen.

Es würde für diejenigen, die es noch in Intervallen verwenden möchten, unbenutzt bleiben und ein unbenutztes Paar verbrauchen, das bisher keine anderen Verwendungszwecke hat, an die ich denken kann.

Verwenden wir nicht die Zuweisungsnotation von R für den Feldzugriff.

Wir könnten möglicherweise -> verwenden, um C / C ++ direkt zu spiegeln und eine neue Syntax für zu erhalten
anonyme Funktionen. Ich habe mich nie sehr um die anonyme Funktion gekümmert
Syntax, da es etwas knapp / unlesbar ist. Vielleicht könnten wir es stattdessen tun
etwas wie

func (x) x ^ 2 end

oder je länger, konsistenter

Funktion (x) x ^ 2 Ende

Vielleicht gibt es eine Möglichkeit, eine gute Syntax zu finden, die nicht erforderlich ist
mit einem Ende.

Die Diskussion nicht zu sehr zu ändern, aber es würde definitiv Sinn machen
zu verwenden -> für echten Feldzugriff.
.

Am Mittwoch, 28. Januar 2015, um 08:49 Uhr, John Myles White [email protected]
schrieb:

Verwenden wir nicht die Zuweisungsnotation von R für den Feldzugriff.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/1974#issuecomment -71857083.

@quinnj : func (x) x^2 end funktioniert bereits. Aber es ist schön, eine sehr präzise Syntax für anonyme Funktionen zu haben: map(x->x^2, 1:10)

Ich denke nicht, dass der Feldzugriff eine spezielle Syntax erfordert (ein Unicode-Zeichen, wie von x -> x^2 verlieren.

Es sieht so aus, als ob dieses Problem noch offen ist / noch aussteht. Es war interessant, all die verschiedenen Kommentare hier zum Punktoperator zu lesen.

Gab es Vorschläge zum Hinzufügen weiterer neuer Operator-Token? Die Verwendung von :> könnte eine gute Alternative sein. Es hat Parallelen zu |> und könnte ein nativeres Julia-Gefühl haben:

c = foo.a + foo.b
pyobj:>obj:>process(c)

Während es immer noch viel einfacher zu schreiben ist als:

pyobj[:obj][:procees](c)

Oder vergleichen:

someobj :> array |> length 
# vs
length(get_array(someobj)) 

Ich bin neu bei Julia, aber ich habe schnell eine starke Wertschätzung für den Ansatz des Mehrfachversands gewonnen. Objektorientiertes Programmieren - insbesondere für das wissenschaftliche Programmieren - macht viele Aufgaben umständlicher. Ich würde mir Sorgen machen, dass das OO-Paradigma / die OO-Syntax die Entwicklung von Julias Stil negativ beeinflussen würde.

Oder alternativ, da ich das Internieren und / oder Zitieren von Strings vergessen habe:

someobj <: field_name |> length

@elcritch , <: wird derzeit als Julias Operator "ist Subtyp von" verwendet, und wenn :> eingeführt wird, ist es wahrscheinlich, dass wir ihn aus diesem Grund für etwas typbezogenes verwenden möchten Erbe.

Wenn die Verwendung von instance..member ein Problem darstellt, gibt es hier einige Möglichkeiten. Schütze deine Augen! Es ist wahrscheinlich, dass jeder von diesen schlimmer ist:

  • instance^.member (Karottenpunkt)
  • instance~.member (Tilde Dot)
  • instance:.member (Doppelpunkt)
  • instance*.member (Sternpunkt)
  • instance-.member (Strichpunkt)
  • [email protected] (At-Sign-Punkt)
  • instance&.member (Amperepunkt)
  • instance$.member (Dollarpunkt)
  • instance<.>member (Raumschiffpunkt)

Ich denke ehrlich, dass (a) .. gut genug scheint und (b) es nicht wirklich wichtig ist, ob es gut aussieht, weil dies immer eine dunkle Ecke der Sprache sein wird. Die meisten Leute werden instance.member weil sie nur entweder ein Feld oder eine getfield -Methode haben, aber nicht beide.

(Im Übrigen werden sich die meisten Leute, die sowohl ein Mitglied als auch eine Methode verwenden möchten, wahrscheinlich nicht einmal die Mühe machen, etwas über .. zu lernen. Sie definieren lediglich eine Methode für foo.member und nennen die "echte" "Feld foo._member . Wahrscheinlich ist dies sowieso ein besserer Stil - es bedeutet, dass beim Lesen der Definition von type sofort klar wird, dass _member nichts sein soll Sie können oder sollten direkt darauf zugreifen. Dies würde dafür sprechen, dass .. etwas Hässliches und Dunkles wie :. anstatt wertvolle Interpunktionsimmobilien in Anspruch zu nehmen.)

Ich würde die Möglichkeit vermissen, .. als Infix-Intervall-Operator zu verwenden, aber überladbarer Feldzugriff ist ein lohnender Kompromiss. Während ich zögere, ein wenig Angst davor zu haben, dem Doppelpunkt weitere Bedeutungen hinzuzufügen, scheint :. nicht so schlimm zu sein.

Beachten Sie, dass :. derzeit eine gültige Syntax für symbol(".") Daher ist es möglicherweise nicht sinnvoll, diese zu verwenden. Der Punkt, dass .. möglicherweise nützlich ist, ist gut aufgenommen; Wir sollten es nicht mit einer Syntax verschwenden, die kaum jemand verwenden wird. Ich würde mich sehr über etwas noch Hässlicheres wie @. freuen (da . weder ein gültiger Makroname ist noch eine Kennung beginnen kann, scheint dies mit nichts in Konflikt zu stehen ). Auch dies wird eine so dunkle Ecke von Julia sein, dass es sich nicht lohnt, zu versuchen, es hübsch zu machen.

+1, um dies mit .. erledigen und mögliche Hässlichkeiten zu ignorieren

Ja, lass uns trotzdem für .. wenn es .. dann denke ich, dass es ein _range_ Konstruktor wäre, aber hey, es ist schon da mit Doppelpunkt, z. start:stop .

Ein letzter Punkt: Was ist mit .: ? Ist es zu subtil, ab, a. (B), a. (: B) und a.:b zu haben?

@ Hayd , das scheint aus Versehen zu subtil und einfach zu bedienen zu sein.

@ihnorton , gibt es eine Chance, eine Version von # 5848 wiederzubeleben? Wir könnten auf die Syntaxfrage eingehen und einfach Core.getfield(x, Field{y}) um auf das "echte" Feld zuzugreifen.

Abgesehen von der Syntax von Core.getfield , gibt es noch wesentliche Fragen?

In # 5848 schlug @tknopp vor, nur "echten" @JeffBezansons Vorschlag, dass alles x::Vector{Any} , kann x[i].y als getfield(x[i], Field{:y}) interpretiert werden, und das Versandsystem wird das Richtige tun, unabhängig davon, ob y ist ein reales Feld. Wenn Sie jedoch nur getfield für "virtuelle" Felder aufrufen möchten, muss der Codegen eine Miniatur-Teilmenge des Versandsystems für die Laufzeitprüfung der x[i] implementieren

Eine andere Frage war, ob Module.foo überladbar sein sollte. Einerseits besteht eine gewisse Konsistenz darin, getfield für alles zu verwenden, und das oben erwähnte Beispiel Vector{Any} könnte Module Array-Mitglieder haben, sodass wir diesen Fall behandeln müssten wie auch immer. Auf der anderen Seite wies @JeffBezanson darauf hin, dass dies die Kompilierung erschweren und das Verhalten von Deklarationen wie function Base.sum(...) schwer zu fassen machen könnte. Meine Präferenz wäre es, Module.foo zumindest Module funktioniert (dh nicht mit einem Vector{Any} ). ;; Die leichte Inkonsistenz scheint es wert zu sein, konservativ zu sein, was geändert wird.

+1, um eine Überladung von Module.foo nicht zuzulassen.

Ein Bereich des wissenschaftlichen Rechnens, in dem OO-Programmierung und -Syntax FP tatsächlich überlegen sind, ist die agentenbasierte Modellierung. Obwohl ich konkrete und mehrfache Vererbung zum Einrichten von Agentenhierarchien vermisse, sind die leichten und schnellen Abstraktionen und das schnelle Prototyping von Julia erstaunlich. Es sind bereits einige ABM-Frameworks aufgetaucht.

In ABM ist die Punktnotation vorzuziehen, um Agenteninteraktionen auszudrücken: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Dies ist offensichtlich nicht der größte Anwendungsfall, aber es wäre schön, diesen syntaktischen Zucker zum Nachdenken und Codieren über ABMs beizubehalten.

Ich würde diese Syntax auch sehr gerne in Julia verfügbar haben. Wie
So sehr ich den funktionsorientierten Ansatz eines Designs schätze
Perspektive, Methodenaufrufsyntax ist sehr nützlich und in mehreren lesbar
Domänen. Es wäre großartig, wenn Ab (C) äquivalent zu b (A, C) wäre.
Am 22. April 2015, 8:50 Uhr, schrieb "datnamer" [email protected] :

Hier ein Bereich des wissenschaftlichen Rechnens, in dem OO programmiert wird
und die Syntax ist FP bei der agentenbasierten Modellierung tatsächlich überlegen. obwohl ich
Verpassen Sie konkrete und mehrfache Vererbung, um Agentenhierarchien einzurichten
leichte und schnelle Abstraktionen und schnelles Prototyping von Julia ist
Erstaunlich - Es sind bereits einige ABM-Frameworks aufgetaucht.

In ABM ist die Punktnotation vorzuziehen, um Agentenwechselwirkungen auszudrücken:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Dies ist offensichtlich nicht der größte Anwendungsfall, aber es wäre schön, dies beizubehalten
syntaktischer Zucker zum Nachdenken und Kodieren über ABMs.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95218555.

In ABM ist die Punktnotation vorzuziehen, um Agenteninteraktionen auszudrücken: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Warum ist das besser? Edit: Ich meine in diesem ABM-Kontext speziell.

Bitte, lassen Sie uns nicht in religiöse Kriege um Rechtschreibung verwickelt werden. @ dbeach24 , niemand schlägt vor, dass a.b(c) in Julia b(a,c) ; das wird nicht passieren.

Überladbare Punkte sind entscheidend für die natürliche Interaktion mit anderen Sprachen. Das ist Grund genug.

Subject.Verb (DirectObject)

Ist in mehreren Zusammenhängen ziemlich natürlich. Viele OO-Programmierer sind es gewohnt
es, und während es eine bloße Neuordnung der Funktion (A, B) ist, diese Neuordnung
tut viel für die Lesbarkeit, IMO.
Am 22. April 2015, 10:32 Uhr, schrieb "Andy Hayden" [email protected] :

In ABM ist die Punktnotation vorzuziehen, um Agentenwechselwirkungen auszudrücken:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Warum ist das besser?

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95256390.

Ich habe es vorgeschlagen. (Entschuldigung, ich wollte keinen Krieg beginnen und wusste es auch nicht
Der Vorschlag wäre unbeliebt.) Ich dachte, ich hätte das schon einmal gesehen
in den Foren, merkte aber nicht, dass es bereits als
schlechte Idee. Darf ich fragen warum? (Kannst du mich auf einen Thread verweisen?)

Vielen Dank.
Am 22. April 2015, 11:09 Uhr, "Steven G. Johnson" [email protected]
schrieb:

Bitte, lassen Sie uns nicht in religiöse Kriege um Rechtschreibung verwickelt werden. @ dbeach24
https://github.com/dbeach24 , niemand schlägt vor, ab (c) zu sein
äquivalent in Julia zu b (a, c); das wird nicht passieren.

Überladbare Punkte sind entscheidend für eine reibungslose Interaktion mit anderen Sprachen.
Das ist Grund genug.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95266671.

Ein Grund , dies nicht zu tun , ist , dass a.b nachschlägt b im Rahmen von a , während b allein sieht bis b in das umschließende Zielfernrohr. Es wäre sehr verwirrend, wenn der gepunktete Zugriff manchmal nicht im linken Objekt nachschlagen würde.

Übrigens wird es als eine Funktion angesehen, dass Funktionen in Julia nicht in Objekten nachgeschlagen werden, sondern im aktuellen Bereich. Ich glaube, die Angst, dass Menschen anfangen würden, Funktionen zu verwenden, die in Objekten nachgeschlagen wurden, ist ein Grund, der die Punktüberladung zurückgehalten hat.

@toivoh , jede Implementierung der @ dbeach24 , der Hauptgrund, die wahllose Verwendung von a.b(c) nicht zu fördern, ist, dass die Sprache und die Bibliotheken zu einem Chaos werden, wenn Sie zu viele Syntaxen haben, um dasselbe zu tun. Es ist besser, eine Schreibweise zu wählen und dabei zu bleiben, und Julias mehrfacher Versand begünstigt b(a,c) weil es klarer ist, dass b nicht von a "besessen" wird - den b(a,c) -Methode wird gleichermaßen durch _both_ a und c .

Die größte Ausnahme ist natürlich das Aufrufen externer Bibliotheken in Sprachen wie Python oder C ++, wo es schön ist, die Punktsyntax der aufgerufenen Bibliothek spiegeln zu können. (Damit zB Leute Dokumentation und Beispiele direkt in Julia übersetzen können, ohne viel zu ändern.)

Bin ich ganz nass, aber würde ab (arg) nicht bedeuten, eine in Feld b von a gespeicherte anonyme Funktion zu nehmen und sie dann mit dem angegebenen Argument auszuwerten?

von meinem Iphone gesendet

Am 22. April 2015, um 17:06 Uhr, schrieb Steven G. Johnson [email protected] :

extern

@ScottPJones Das funktioniert derzeit

Ich war nicht besorgt über den Stil oder nicht, ich dachte nur, da es bereits eine Bedeutung hatte, die mit der Arbeitsweise von Julia übereinstimmte (dh in der Lage zu sein, anonyme Funktionen in Feldern zu speichern), war es ein gutes Argument dafür versuche ab (arg) so zu behandeln, als wäre es b (a, arg).
Ich könnte eine Verwendung für eine Struktur (Typ) mit Mitgliedern haben, die anonyme Funktionen speichern, wobei ich in Julia geschriebene Funktionen aus einer Datenbank lade, sie dann analysiere und die Funktionen in einem Objekt speichere ...
Was wäre ein besserer „julianischer“ Stil, um so etwas zu tun?

Vielen Dank!

@ScottPJones Ich denke, es besteht Einigkeit darüber, dass diese nicht gleichwertig sein sollten *.

Es kann Ausnahmen von der "Stil" -Regel geben, aber es muss einen zwingenden Fall geben, um eine Funktion in ein Feld zu setzen, genau wie beim Überladen von Punkten. Ich denke, das Problem ist, dass die Leute dies nicht wohl oder übel tun sollten, um es zu tun.

Das könnte ein Beispiel sein, aber es könnte auch einen besseren Weg geben (sicherlich ist es nicht der einzige Weg) ...

In 99% der Fälle ist es besser, nach Typ (a) zu versenden. Keine Funktionsfelder, keine Punktüberladung.

* _Jedoch denke ich, dass jeder weiß, sobald es landet, wird es ein Paket geben, das genau das tut ..._

In D haben sie sogar den Namen "Uniform Function Call Syntax" für a.b(arg) und es ist ziemlich beliebt, aber ich denke, es steht ziemlich im Widerspruch zu der generischen Funktion, der Mehrfachversandweise von Julia. Wenn die fraglichen Funktionen anonym oder vollständig vom Typ Ente sind, werden die Dinge vermutlich funktionieren, aber das ist IMO ziemlich restriktiv. Ich glaube nicht, dass es viele Gründe gibt, Funktionsfelder in einem zusammengesetzten Typ zu speichern, außer aus Gewohnheit von klassenbasierten traditionellen OO-Sprachen. Ein besserer Ort zum Speichern generischer Funktionen, wenn Sie sie von irgendwoher laden, sind Module.

Aber wir sind jetzt ziemlich weit von "inhaltlichen Fragen" entfernt. Ich bin auch dafür, konservativ zu sein, wie wir das Überladen von getfield zulassen, das Überladen von getfield auf Modulen nicht zulassen und uns nicht zu viele Gedanken über die spezielle Syntax für "true getfield" machen.

@stevengj : Ja, und wie ich sagen wollte, ist dies ein grundlegender Grund, warum es niemals passieren wird, dass a.b(c) gleich b(a, c) , unabhängig davon, was wir sonst mit Punktüberladung tun .

Es gab einige Diskussionen auf der Mailingliste, die dies berührten. Aus meiner Sicht ist der relevanteste Beitrag (von @nalimilan) zu diesem Thread: https://groups.google.com/d/msg/julia-users/yC-sw9ykZwM/-607E_FPtl0J

Wenn ich @johnmyleswhites Kommentar zur persönlichen Richtlinie zur Verwendung dieser Funktion hinzufüge , getfield() keine Nebenwirkungen haben sollte und setfield!() sollte idempotent sein (dh ein mehrmaliger Aufruf mit demselben Wert sollte den gleichen Effekt haben wie ein einmaliger Aufruf). Nicht unbedingt feste Regeln, die vom Compiler durchgesetzt werden, sondern Verwendungsrichtlinien, um zu verhindern, dass Dinge zu verrückt werden.

Ich habe eine Problemumgehung mit Parametertypen mit Zeigerparametern veröffentlicht und konvertiert, um beim Festlegen eines Felds einen benutzerdefinierten Setter aufzurufen:
Beitrag: https://groups.google.com/forum/#!topic/julia -users / _I0VosEGa8o
Code: https://github.com/barche/CppWrapper/blob/master/test/property.jl

Ich frage mich, ob ich diesen Ansatz in meinem Paket als Problemumgehung bis zum Setfield verwenden sollte! Überlastung ist verfügbar, oder ist das parametrische Typsystem zu stark belastet?

Ich möchte einen zusätzlichen Vorteil der Überladung von getfield / setfield! erwähnen. Ich hoffe, dies ist der richtige Ort dafür. Es tut mir sonst leid. (Ein verwandtes Thema wurde unter https://groups.google.com/forum/#!topic/julia-users/ThQyCUgWb_Q angezeigt.)

Julia mit getfield / setfield! Überladen würde eine überraschend elegante Implementierung der Autoreload-Funktionalität in einem externen Paket ermöglichen. (Sehen Sie sich all die harte Arbeit an, die in die IPython-Autoreload-Erweiterung https://ipython.org/ipython-doc/3/config/extensions/autoreload.html gesteckt werden musste, um diese Funktionalität zu erhalten.) Die Idee des Autoreloads ist, dass Sie kann Funktionen und Typen in externen Modulen während der Arbeit mit der REPL ändern.

TLDR: getfield / setfield! Überladung, Wörterbücher und ein Paket ähnlich https://github.com/malmaud/Autoreload.jl sollten den Trick machen.


Stellen Sie sich genauer gesagt ein Paket vor, das Autoreload.jl ähnelt und Folgendes ausführt.

Sie erstellen zunächst ein Modul M.jl:

module M
type Foo
  field1::Int64
end
bar(x::Foo) = x.field1 + 1.0
end

In der REPL geben Sie ein

julia> using Autoreload2
julia> arequire("M")
julia> foo = Foo(42)

Dann ändern Sie M.jl in

module M
type Foo
  field1::Int64
  field2::Float64
end
bar(x::Foo) = x.field1+x.field2

Dies würde automatisch geladen und transformiert werden

# type redefinition removed as already done by Autoreload.jl
const field2_dict = Dict{UInt64,Float64}()
setfield!(x::Foo, ::Field{:field2}, value) = field2_dict[object_id(x)] = value
getfield(x::Foo, ::Field{:field2}) = field2_dict[object_id(x)]
<strong i="25">@do_not_inline</strong> bar(x::Foo) = x.field1 + x.field2

und dann in der REPL könnten Sie tun

julia> foo.field2 = 3.14
julia> println(bar(foo)) # prints 45.14

Die Leistung wäre nicht schlechter als bei Python, sodass Personen, die ihren Workflow vom IPython-Autoreload migrieren, nichts an Leistung verlieren. Und sobald Sie die REPL neu starten, sind Sie wieder voll leistungsfähig.

Ich habe es satt, a[:field][:field2][:morestuff](b[:random_stuff]) schreiben, da es nicht wirklich lesbar ist. Also habe ich dieses kleine Makro geschrieben, das für meine Anwendungsfälle in 0.4 und 0.5 funktioniert
https://github.com/sneusse/DotOverload.jl

TL; DR
Ein Makro, das den AST eines Ausdrucks a.b -> getMember(a, :b) transformiert

Entfernen von 0.6, da kein Konsens darüber besteht, dass dies eine gute Idee ist, und es einen widersprüchlichen Vorschlag gibt, was mit der Punktsyntax zu tun ist.

@ Keno : Haben Sie einen Link zu dem widersprüchlichen Vorschlag?

Ich glaube nicht, dass @StefanKarpinski es schon geschrieben hat, aber ich würde erwarten, dass es bald einen Julep darüber gibt.

Ich fand object.fieldname schöner als Getter-Funktionen wie fieldname(object) oder get_fieldname(object) . Vielleicht haben object.fieldname (oder object$fieldname ) einen Anruf bei getpublicfield (vielleicht mit einem besseren Namen) und object..fieldname sind die tatsächlichen getfield (privat) könnte eine gute Option sein. Auf diese Weise sollten Typen getpublicfield anstelle von Getter definieren, und der Versuch, object.fieldname auszuführen, sollte eine Fehler-ID ergeben, für die das Feld privat ist (es ist privat, wenn es keine Definition für hat getpublicfield ).

Ich habe das Entscheidungsetikett hinzugefügt. Dieses Problem wurde ausführlich erörtert und muss entweder durchgeführt werden oder nicht. Beim Lesen von # 5848 schienen @JeffBezanson @StefanKarpinski und @stevengj dies zu wollen. Wenn ja, sollte dieses Problem einen Meilenstein erreichen, damit es nicht vergessen wird. Ansonsten schließen. Auf jeden Fall denke ich, dass dies eine Änderung ist, die vor 1.0 durchgeführt werden sollte.

@ JeffBezanson und ich haben das gestern gerade Module zulassen (was speziell behandelt wird); (iii) Geben Sie keine spezielle Syntax für Core.getfield (da es nicht dringend erforderlich ist, dass ein überladenes getfield den gleichen Namen wie ein "echtes" Feld hat; letzteres kann einfach damit beginnen ein Unterstrich).

@stevengj : Klingt nach einem vernünftigen Plan. Können Sie angeben, ob dies auf ein einzelnes Argument beschränkt ist oder ob die Version a.fieldname(b) mehreren Argumenten ebenfalls unterstützt werden soll? Dies wird eine Schlussfolgerung zu der obigen Diskussion ziehen. Darüber hinaus wäre es großartig, ein entsprechendes Meilensteinetikett anzubringen (1.0?). Vielen Dank!

Jeff und ich haben den Fall mit mehreren Argumenten nicht besprochen. Mein Gefühl ist, dass wir es genauso gut unterstützen könnten, da Sie es trotzdem simulieren können, indem Sie eine Funktion aus dem Fall ohne Argumente zurückgeben (aber es ist aus demselben Grund nicht kritisch, dies sofort zu tun).

Ich benutze einen Konverter, um Werte umzuwandeln und Daten zu validieren.
so was:

abstract AbstractAge{T}
abstract AbstractPerson
type PersonAge <: AbstractAge{AbstractPerson} 
    value::Int64
end

Base.convert(t::Type{AbstractAge{AbstractPerson}}, value::Int64) =  begin
  if value < 140 && value > 0
    PersonAge(value) 
  else
     throw(ErrorException("ValueError"))
  end
end

type Person <: AbstractPerson
  age::AbstractAge{AbstractPerson}
end 

a = Person(32)
a.age = 67

Hier ist eine unterhaltsame 3-Zeilen-Implementierung:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Ich denke, dass a.b tatsächlich eine Projektionsfunktion Field{:b}() anstelle von getfield aufrufen sollte, damit Sie Funktionen wie x->x.a bereits kostenlos erhalten. Dadurch kann getfield auch immer einen Feldzugriff auf niedriger Ebene bedeuten.

Die obige Implementierung funktioniert vollständig, ist aber für den Compiler ziemlich schwierig (sysimg + 5%, was wirklich eine angenehme Überraschung ist). Dies erfordert also einige Spezialisierungsheuristiken und einige frühe Optimierungen müssen aktualisiert werden, aber dann wird dies hoffentlich realisierbar sein.

Ich bin überrascht, dass mit dieser Implementierung Tests bestanden werden konnten. Macht es diese Semantik Codegen nicht unmöglich, Modulreferenzen zu optimieren? Es scheint auch, dass globale Variablen dadurch viel teurer werden (Geschwindigkeit und Speicher). Es scheint nicht allzu wahrscheinlich, dass dies im Sysimg auftaucht. Obwohl die Inferenzverschlechterung das Sysimg hier hätte verkleinern sollen, klingt 5% schlechter nicht nach einem guten Start.

Ja, in jl_resolve_globals ist Code enthalten, um diese in GlobalRefs zu konvertieren, die aktualisiert werden müssten. Davon abgesehen sollten diese eingebunden werden, damit Codegen damit umgehen kann. In der Schlussfolgerung brauchen wir wahrscheinlich nur einen Sonderfall ähnlich dem für Tupel getindex .

Wir brauchen wahrscheinlich nur einen speziellen Fall ähnlich dem für Tupel getindex

In diesem Sonderfall wird gehofft und angenommen, dass keine Methode definiert wird, die sich mit der integrierten Methodensignatur getindex überschneidet. Ich sehe nicht, wie dieser Fall hier besonders anwendbar ist.

Ich würde eine Methode für ::Module hinzufügen und sagen, dass Sie sie nicht beschatten dürfen - möglicherweise mit einem tatsächlichen Fehler erzwungen.

@ JeffBezanson Hast du einen Zweig mit diesen drei Zeilen? Ich habe versucht, sie selbst zu einem lokalen Build hinzuzufügen, aber Julia scheint inzwischen weitergezogen zu sein, und ich konnte es nicht zum Laufen bringen.

Ich sollte sagen, wenn ich nur einen Wunsch nach einer Funktion in Julia 1.0 hätte, wäre dies der richtige. Es würde zwei langjährige Probleme lösen, die ich sowohl in Mimi als auch in Query habe .

Der Fall für Query ist meiner Meinung nach ziemlich allgemein. Ich glaube, man könnte damit eine Version von NamedTuples schreiben, die keine neuen Typen in Makros generieren musste, und die es mir wiederum ermöglichen würde, generierte Funktionen zu schreiben, die ein NamedTuple mit einer Reihe von Feldern zurückgeben in der generierten Funktion berechnet. Ich denke, das würde es mir ermöglichen, eine typstabile Version von blackrock / NamedTuples.jl # 4 zu schreiben, was bei weitem mein derzeit größter Stolperstein in Query.jl ist.

Lange Rede, kurzer Sinn, das wäre super, super wertvoll für die gesamte Datenverarbeitungsgeschichte in Julia.

Ist die folgende Syntax verfügbar?

function (obj::MyType).plus(n::Int)
       return obj.val + n
end

Warum in aller Welt möchten Sie diese Syntax?

obj.plus(n) anstelle von obj + n schreiben zu wollen, ist ein ernstes oo Stockholm-Syndrom.

Ok, das gegebene Beispiel war trivial (und schlecht).
Es war nur eine Idee, diese Syntax zu verwenden, anstatt getfield zu überladen und Argumente verwenden zu können.

Mir geht es hauptsächlich um die Funktionalität. Eine abgekürzte Syntax zum Überladen von getfield scheint viel weniger wichtig zu sein und es lohnt sich nicht, sich darüber zu streiten. Darüber hinaus spiegeln getfield und setfield! direkt getindex und setindex! und sind daher in Julia etwas natürlich. Schließlich passt die umfassende Verwendung eines OO-Stils nicht wirklich zu Julias Redewendung, und ich glaube nicht, dass wir ihn durch eine OO-ähnliche Syntax zur Methodendefinition fördern möchten ( siehe jedoch meinen obigen Kommentar zu Argumenten).

Eine Sache, die mir einfiel, war, dass der Operator $ veraltet war. Dies macht es offensichtlich schon möglich, so etwas wie $(x, sym::Symbol) = ... tun, aber wir könnten auch ein ausgefalleneres syntaktisches Umschreiben wie:

x$y          => $(x, ::Type{Val{:y}})
x$z(args...) => $(x, ::Type{Val{:z}}, args...)

Ich denke, dass dies die meisten der in dieser Ausgabe erwähnten Fälle bereits abdeckt, ohne dass eine vollständige Überlastung des Getfields erfolgt. Ehrlich gesagt ist der Operator . in Julia ziemlich semantisch gesättigt, so dass sich so etwas leichter verdaulich anfühlt und praktisch genug ist, um dennoch nützlich zu sein.

@quinnj, bereits in # 18696 vorgeschlagen.

Da wir schon haben . für Feldzugang, jedoch scheint es unelegant zwei Feldzugriff ähnlichen zu haben Betreiber, ein überladbaren und einer nicht. Und es wäre ein bisschen unnatürlich für inter Sprache Anrufe zu Python und anderen OO - Sprachen sein, die fast universell verwenden . .

Ich sehe die Interoperabilität mit anderen Sprachen nicht als gültiges Argument für die Einführung von so etwas. Es ist wie zu sagen: "Python-Code sieht so aus. Um also vorgeben zu können, Python zu sein, sollten wir dies auch tun." Ich habe noch kein Argument dafür gesehen, das Julia selbst besser und / oder konsistenter machen wird. Es ist bereits bekannt, dass Julia die OOP-Syntax x.f() nicht bereitstellt. solche Dinge zuzulassen, verlangt nach Inkonsistenz.

@stevengj , ein Teil x.f _ kein Feldzugriff ist. Es gibt kein tatsächliches Feldmitglied f . In dieser ganzen Ausgabe geht es um die Überlastung von getfield, und ich denke, die Hauptbedenken sind die potenzielle Verwirrung darüber, ob sich x.f tatsächlich auf ein Feld bezieht oder etwas anderes unter der Haube tut.

Der Vorteil des von mir vorgeschlagenen syntaktischen Umschreibens besteht darin, dass es die Interop-Story der Sprache erfüllt, ohne zusätzliche Verwechslungen mit getfield zu verursachen. Darauf bezog ich mich, als ich erwähnte, dass . übersättigt sein würde. Wäre x$f wirklich so viel umständlicher? Ich verstehe nur nicht, warum dies den Punktoperator verwenden muss.

Bei diesem ganzen Problem geht es um die Überlastung von getfield, und ich denke, die Hauptbedenken sind die mögliche Verwirrung darüber, ob sich x.f tatsächlich auf ein Feld bezieht oder etwas anderes unter der Haube tut.

Ich glaube nicht, dass der dreizeilige Vorschlag von @JeffBezanson zu Verwirrung a.b wird immer in einen Projektionsfunktionsaufruf und niemals in einen getfield -Aufruf gesenkt. Und dann gibt es eine Standard- / Fallback-Methode in der Basis für die Projektionsfunktion, die getfield aufruft. Benutzer können dieser Projektionsfunktion Methoden für ihre eigenen Typen hinzufügen, wenn sie möchten. getfield bedeutet in diesem Vorschlag immer einen Feldzugriff auf niedriger Ebene, und Benutzer würden getfield keine Methoden hinzufügen (ich nehme an, das haben Sie mit "Überladen von getfield" gemeint). Das scheint mir sehr sauber zu sein. Trotzdem ist mir nicht klar, ob dieser Vorschlag noch auf dem Tisch liegt oder nicht, es scheint einige Compiler-Bedenken zu geben, die ich nicht verstehe :)

Ich würde dieses Feature wirklich gerne haben (ich habe es in Scala immer geliebt). IMHO-Zugriff auf Felder direkt ist in Julia sehr natürlich (im Vergleich zu z. B. der Java-Methode, Getter und Setter für alles defensiv zu definieren). Ohne die Möglichkeit, "virtuelle" Felder hinzuzufügen, wird es jedoch sehr schwierig, Strukturen zu entwickeln / umzugestalten, ohne viel Code zu beschädigen.

Ich weiß, dass dies mit v2.0 markiert wurde, aber ich möchte dies noch einmal ansprechen, in der Hoffnung, dies für v1.0 zu überdenken. Diese Funktion ist für Paket- / Bibliotheksentwickler sehr wertvoll.

Wenn Sie ein Paket für Benutzer erstellen, können Paketverwalter durch Überladen des Attribut-Accessors eine wesentlich übersichtlichere Benutzeroberfläche präsentieren. Ich würde behaupten, dass,

complex_data_structure.attribute

ist einfach zu tippen als

get(complex_data_structure, :attribute)

Dabei ist get eine Funktion, die erforderlich ist, um Daten abzurufen, die durch :attribute .

Dies könnte möglicherweise auch die Erkennbarkeit in der REPL verbessern und würde der Erforschung von Sprachcode und Daten zugute kommen.

Darüber hinaus wäre ein Intercepter für Setterattribute unglaublich wertvoll. Betrachten Sie das folgende Beispiel (das derzeit bereits in vielen meiner Skripte enthalten ist :)).

set(complex_data_structure, :attribute, modify_attribute(get(complex_data_structure, :attribute), additional_arguments))

Wenn ein Attribut-Getter und ein Setter als Teil von Julia enthalten wären, würde das Folgende das Folgende werden.

complex_data_structure.attribute = modify_attribute(complex_data_structure.attribute, additional_arguments)

Dies ist meiner bescheidenen Meinung nach viel besser lesbar. (Nebenbemerkung: Ich könnte eine Pipe-Komposition verwenden, um sie besser lesbar zu machen, aber die additional_arguments würden es dort wieder komplizieren. Und das Argument hier würde immer noch bestehen.)

Darüber hinaus ist das Einschränken von Werten, die von einem Benutzer bereitgestellt werden, ein sehr häufiger Anwendungsfall, und dies ist in Version 1.0 sehr hilfreich. Dadurch werden die Benutzeroberflächen für mehrere Pakete erheblich verbessert. Derzeit scheint es nach meinem besten Wissen keine Möglichkeit zu geben, Folgendes zu tun (kann mich jemand korrigieren, wenn ich falsch liege)?

module point

mutable struct Point
    x::Int
    y::Int
    function Point(x, y)
        if x < 0 || y < 0
            throw(error("Only non-negative values allowed"))
        end
        this = new(x, y)
    end
end

end
# point

p1 = point.Point(-1, 0)
# Only non-negative values allowed

# Stacktrace:
# [1] point.Point(::Int64, ::Int64) at ./In[30]:8
# [2] include_string(::String, ::String) at ./loading.jl:515

p1 = point.Point(0, 0);
p1.x = -1;
p1
# point.Point(-1, 0)

Der Konstruktor kann die Domäneneingaben auf eine unveränderliche Struktur beschränken. Ein Benutzer kann jedoch weiterhin ungültige Werte eingeben. Attribut-Getter und -Setter würden hier helfen, da sie das Feedback zur Datengültigkeit hier unmittelbarer machen würden, und dies würde zu einer viel saubereren Oberfläche als Benutzer der Sprache führen.

Dies wird auch anderen Paketen wie PyCall.jl und DataFrames.jl zugute kommen, um wohl bessere und intuitivere Benutzeroberflächen für Benutzer zu erstellen. Die Autoren dieser Pakete haben ebenfalls Interesse an dieser Funktion bekundet.

Gedanken? Beim Durchlesen des Threads scheint dies bereits ziemlich kurz vor dem Finale zu stehen. Ich bin gespannt, was die Autoren darüber denken.

Hier in einem typstabilen optionalen Makro implementiert: https://github.com/bramtayl/DotOverloading.jl. Ich denke, es hat Potenzial für Base.

@bramtayl , ich denke, @overload_dots vor a.b Ausdrücken setzen zu müssen, macht den Punkt hier zunichte .

Ich habe nicht vorgeschlagen, das Makro zur Basis hinzuzufügen. Ich schlug vor, die vom Makro verwendete Strategie in den Parser einzubinden. Es ist jedoch ein Makro, das für ganze Dateien ausgeführt oder sogar in eine IDE gehackt werden kann.

Das würde bedeuten, dass a.b auf Expr(:., :a, :(Val{:b}()) analysiert und auf get_field_overloadable(a, Val{:b}()) gesenkt wird

@bramtayl , siehe Jeffs Implementierung oben .

Ein Problem beim Überladen von getfield besteht darin, dass einige Bibliotheken keine vernünftige Schnittstelle zu den Interna einer Datenstruktur haben. Wenn ich nur einen Datenpunkt in einer Struktur ändern möchte, damit ich meinen Code ausführen und meinen Bericht für morgen erstellen kann, freue ich mich nicht besonders darauf, Code in einer Bibliothek drei Ebenen tief in meiner Abhängigkeit zu bearbeiten, damit ich in der Lage bin auf vernünftige und schnelle Weise direkt auf den Datenpunkt zuzugreifen. Ich möchte das Gefühl haben, die Kontrolle über die von mir verwendeten Datenstrukturen zu haben.

Der zweite Punkt ist, dass ich bei Verwendung von getfield und setfield eine vernünftige Erwartung haben möchte, dass ich direkt auf die Datenstruktur zugreife, anstatt auf einen riesigen Funktionsmechanismus. Außerdem wird das Schlüsselwort struct verwendet, um einen Typ zu definieren, der Sie an C erinnert, und ich glaube, es gibt Ihnen die Erwartung, dass der Zugriff auf getfield direkt sein sollte.

Daher denke ich, dass die Verwendung des Operators $ stattdessen ein vernünftiger Kompromiss ist, da nicht erwartet wird, dass der Zugriff direkt wie in getfield erfolgt. In anderen Kontexten ist er bereits ein Sonderzeichen und daher nicht allzu überraschend Wenn es auf diese Weise verwendet wird, ist es leicht zu verwerfen, weil nur wenige Leute es verwenden und weil es keine Funktion mehr ist und es in R ähnlich verwendet wird.

Ein Problem beim Überladen von getfield besteht darin, dass einige Bibliotheken keine vernünftige Schnittstelle zu den Interna einer Datenstruktur haben.

Die Implementierung von @JeffBezanson von oben überlastet getfield (oder setfield ) nicht.

Ich möchte auch dafür plädieren, dass es 1.0 wird. Ich bin oft hin- und hergerissen zwischen dem Schreiben von Gettern / Einstellungen, um meinen Code etwas flexibler zu halten (und mich dann zu fragen, wie ich sie benennen soll) oder dem "Zulassen / Ermutigen" des Benutzers meines Codes, direkten Feldzugriff zu verwenden.

Das defensive Schreiben vieler Getter und Setter im Vorfeld scheint mir nicht sehr julianisch zu sein - schließlich hat Julia keine privaten / geschützten Felder und ermutigt den Benutzer, direkt auf Felder zuzugreifen. Dann stellt sich die Frage, wie man viele Getter- / Setter-Funktionen benennt, ohne viele Konflikte mit anderen Paketen zu riskieren. Und die Alternative, Methoden für getfield und setfield! (oder ähnliches) hinzuzufügen, die auf Val{:fieldname} oder so versendet werden, scheint auch nicht sehr benutzerfreundlich zu sein.

Wenn der direkte Feldzugriff jedoch im Grunde alle Strukturen für immer sperrt, sollte dies eindeutig nicht empfohlen werden - insbesondere bei langlebigem Code. Die Implementierung von @JeffBezanson scheint ein guter Ausweg aus diesem Dilemma zu sein.

Richtig, @davidanthoff. Ich muss die Überladung von getfield und die Überladung der Feldzugriffssyntax . . Ich meinte Überladung der Feldzugriffssyntax.

Es gibt abwärtskompatible Möglichkeiten, diese Funktion hinzuzufügen, sodass sie in einer 1.x-Version hinzugefügt werden kann, in 1.0 jedoch nicht. Wir haben weder ein fertiges Design noch Zeit, es umzusetzen, selbst wenn wir eines hatten.

Es wäre äußerst praktisch, die Punktsyntax für Zeiger auf (C-) Strukturen zu verwenden.

Meine bevorzugte Syntax wäre, Punkte nur zum Verschieben und Konvertieren von Zeigern und [] für deref zu verwenden.

Strukturiere also somepair a :: Int b :: Int end und p :: Ptr {somepair}, dann ist pa ein Zeiger auf das a-Feld, und ich schreibe mit pa [] = 3 darauf oder lese mit x = pa [].

Als nächstes brauchen wir nur Vorwärtsdeklarationen von Typen und möglicherweise eine Möglichkeit, C-Header zu importieren, und dann wird das Umschließen von C zum Kinderspiel.

Ps, um zu verdeutlichen, so etwas wie:

function getfield(p::Ptr{T}, fn::Symbol) # dispatch on values of T and fieldname
ftype = fieldtype(T, fn)
offset = fieldoffset(T,fn)
return convert(Ptr{ftype}, p+offset)
end

getindex(p::Ptr{T}) where T = unsafe_load(p)
setindex!(p::Ptr{T}, v) where T = unsafe_store!(p, convert(T,v))

Für Bonuspunkte könnten die Typen aus Julias Laufzeitbibliothek exportiert werden, und pointer_from_objref könnte uns tatsächlich einen gut getippten Zeiger für eine bequemere Selbstbeobachtung geben.

Ich habe das Gefühl, dass Gewerkschaften irgendwie automatisch arbeiten sollten, nur indem sie zwei Felder mit dem gleichen Versatz haben?

@chethega Ich denke, Sie suchen nach https://github.com/JuliaLang/julia/pull/21912 , das ungefähr die gleiche Funktion bietet, ohne die Probleme bei der Übernahme des Speichermodells von C (Verstöße gegen Typ-Punning, Out-of) -gebundener Zugriff, Aliasing von inneren Zeigern usw., die die in dieser Sprache möglichen Leistungsoptimierungen einschränken).

Macht eine konstante Ausbreitung dies praktikabler?

Bitte ändern Sie Meilenstein auf 1.0! :) :)

@ Liso77 Was meinst du? Dies ist bereits auf dem Git-Master implementiert, wie im Status beim Schließen angegeben.

@nalimilan sorry wenn ich es falsch verstehe! Aber ich dachte, dass als 1.x als verschobene Dinge bezeichnet werden, die nach 1.0 gelöst werden. Und das ist jetzt gelöst ...

Open Source ist eine dezentrale Community. Der Meilenstein legt fest, was bis 1.0 abgeschlossen sein soll, aber die Mitwirkenden und arbeiten an dem, was sie wollen. In diesem Fall wollte jemand dies in 1.0, also hat er den Code beigesteuert, um ihn dorthin zu bringen.

@ Liso77 Soweit ich weiß, wird dies nicht in

Nein, es wird in Master zusammengeführt und wird in der nächsten Version enthalten sein.

:) Gut! Ich dachte nur, dass es gut ist, 1.0 zu beschriften, was in 1.0 herauskommt. Wenn es nicht erwünscht ist, entschuldigen Sie bitte die Störung! :) :)

Ich denke, die NEWS-Datei ist eine bessere Möglichkeit, um zu sehen, was in 1.0 vor sich geht.

Der Meilenstein wurde hinzugefügt, um zu bedeuten, "kann implementiert werden, ohne die Kompatibilität in der 1.x-Release-Serie zu beeinträchtigen", aber da der Code fertig war, wurde er trotzdem vor dem Einfrieren der 1.0-Funktionen zusammengeführt. Ich habe den Meilenstein aus Gründen der Übersichtlichkeit entfernt, aber zu diesem Zeitpunkt ist alles, was im Master zusammengeführt wird, in 1.0.

Danke für die Klarstellung! Das ist spannend! Besonders aufschlussreich war auch die NEWS-Datei.

Danke fürs Entfernen! Ich bin auch sehr froh, dass es in 1.0 kommen wird! :) :)

Ich frage mich, ob es eine Möglichkeit gibt, diese neuen "dynamisch definierten Felder" bei der automatischen Vervollständigung zu unterstützen, z. B. indem man fieldnames überladen lässt. Dies kann für die interaktive Verwendung sehr leistungsfähig sein, z. B. beim Umgang mit DataFrame s (vorausgesetzt, sie werden in Zukunft df.column_name ) mit vielen Spalten und / oder langen Spaltennamen.

Ich denke im Moment schauen sich REPL (Tab-Erweiterung), IJulia usw. die Typdefinition an, nicht die Instanz? Aber vielleicht könnte das für den interaktiven Gebrauch geändert werden. Es ist jedoch wahrscheinlich unmöglich, eine IDE wie Juno zu unterstützen, da Instanzen während des Codierens nicht verfügbar sind.

@oschulz , du kannst schon fieldnames überladen:


julia> struct Foo; foo; end

julia> fieldnames(Foo)
1-element Array{Symbol,1}:
 :foo

julia> Base.fieldnames(::Type{Foo}) = [:bar, :baz]

julia> fieldnames(Foo)
2-element Array{Symbol,1}:
 :bar
 :baz

Und es sieht so aus, als ob fieldnames ist, was die REPL betrachtet:

julia> x = Foo(3)
Foo(3)

julia> x.ba<tab>
bar baz

@yurivish richtig - aber ist es derzeit "sicher"? Ich bin mir nicht sicher, was sonst noch von fieldnames .

Wenn dies nicht sicher ist, sollte es möglich sein, ein complete_fieldnames(x) = fieldnames(x) zu definieren, complete_fieldnames für die REPL-Vervollständigungen zu verwenden und dieses für benutzerdefinierte Vervollständigungen zu überladen.

Ja, aus diesem Grund habe ich dies angesprochen - für den Fall, dass etwas in Base geändert / hinzugefügt werden muss, damit REPL und Freunde es später nutzen können. In Anbetracht der 0.7 Feature Freeze ...

Deshalb bin ich froh, dass wir die Funktion getproperty aufgerufen haben; es hilft zu verdeutlichen, dass es sich nicht auf dasselbe bezieht wie fieldnames . Eine Störung von fieldnames kann definitiv ernsthafte Probleme verursachen.

Möglicherweise benötigen wir ein entsprechendes propertynames , um die gültigen Eigenschaftswerte anzugeben. Natürlich ist das möglicherweise kein genau definiertes Konzept. Vielleicht möchten wir etwas Spezifischeres für die Fertigstellung.

Ich hatte das Gefühl, dass dies möglicherweise eine etwas tiefere Analyse und Eingabe durch die Experten erfordert (danke!) - sollten wir dafür eine neue Ausgabe eröffnen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen