Julia: Funktionsverkettung

Erstellt am 27. Jan. 2014  ·  232Kommentare  ·  Quelle: JuliaLang/julia

Wäre es möglich, das Aufrufen einer Funktion auf Any zuzulassen, sodass der Wert als erster Parameter an die Funktion übergeben wird und die an den Funktionsaufruf für den Wert übergebenen Parameter anschließend hinzugefügt werden?
Ex.

sum(a::Int, b::Int) -> a + b

a = 1
sum(1, 2) # = 3
a.sum(2) # = 3 or
1.sum(2) # = 3

Ist es möglich, deterministisch anzugeben, was eine Funktion zurückgibt, um Laufzeitausnahmen zu vermeiden?

Hilfreichster Kommentar

Unsere aktuelle Liste verschiedener Bemühungen usw.
Ich denke, es lohnt sich, diese zu überprüfen (idealerweise vor der Stellungnahme, aber w / e).
sie sind alle etwas unterschiedlich.
(Ich versuche chronologisch zu bestellen).

Pakete

Prototypen ohne Paket

Verbunden:


Vielleicht sollte dies in einem der Top-Beiträge bearbeitet werden.

aktualisiert: 2020-04-20

Alle 232 Kommentare

Die Syntax . ist sehr nützlich, daher wird sie nicht nur als Synonym für Funktionsaufruf verwendet. Ich verstehe den Vorteil von 1.sum(2) gegenüber sum(1,2) . Für mich scheint es die Dinge zu verwirren.

Ist die Frage nach Ausnahmen ein separates Thema? Ich denke, die Antwort ist nein, abgesehen davon, dass ein Funktionskörper in try..catch eingeschlossen wird.

Das Beispiel 1.sum (2) ist trivial (ich bevorzuge auch sum (1,2)), aber es soll nur zeigen, dass eine Funktion per se nicht im Besitz dieses Typs ist, z. 1 kann an eine Funktion übergeben werden, wobei der erste Parameter ein Real ist, nicht nur an Funktionen, die erwarten, dass der erste Parameter ein Int ist.

Bearbeiten: Ich könnte Ihren Kommentar falsch verstanden haben. Punktfunktionen sind nützlich, wenn bestimmte Entwurfsmuster angewendet werden, z. B. das für die Konfiguration häufig verwendete Builder-Muster. Ex.

validate_for(name).required().gt(3) 
# vs 
gt(required(validate_for(name)), 3) 

Die Ausnahmen, auf die ich mich gerade bezog, sind auf Funktionen zurückzuführen, die nicht deterministische Ergebnisse zurückgeben (was ohnehin eine schlechte Praxis ist). Ein Beispiel wäre der Aufruf von a.sum (2) .sum (4), wobei .sum (2) manchmal einen String anstelle eines Int zurückgibt, aber .sum (4) einen Int erwartet. Ich gehe davon aus, dass der Compiler / die Laufzeit bereits intelligent genug ist, um solche Umstände zu bewerten - was beim Verschachteln der Funktionssumme (Summe (1, 2), 4) gleich wäre -, aber die Feature-Anforderung würde eine Erweiterung dieser Funktionalität erfordern, um Typbeschränkungen durchzusetzen Punktfunktionen.

Einer der Anwendungsfälle, die Menschen zu mögen scheinen, ist die "fließende Schnittstelle". In OOP-APIs ist es manchmal hilfreich, wenn Methoden das Objekt zurückgeben, sodass Sie beispielsweise some_obj.move(4, 5).scale(10).display()

Für mich denke ich, dass dies besser als Funktionszusammensetzung ausgedrückt wird, aber das |> funktioniert nicht mit Argumenten, es sei denn, Sie verwenden anon. Funktionen, zB some_obj |> x -> move(x, 4, 5) |> x -> scale(x, 10) |> display , was ziemlich hässlich ist.

Eine Option, um diese Art von Dingen zu unterstützen, wäre, wenn |> die LHS als erstes Argument an die RHS schob, bevor sie ausgewertet wurde, aber dann konnte sie nicht als einfache Funktion implementiert werden, wie sie jetzt ist.

Eine andere Option wäre eine Art @composed -Makro, das dem folgenden Ausdruck diese Art von Verhalten hinzufügt

Sie können die Verantwortung für die Unterstützung auch auf Bibliotheksdesigner verlagern, wo sie diese definieren können

function move(obj, x, y)
    # move the object
end

move(x, y) = obj -> move(obj, x, y)

Wenn Sie also kein Objekt angeben, wird eine Teilfunktionsanwendung ausgeführt (indem eine Funktion mit einem Argument zurückgegeben wird), die Sie dann innerhalb einer normalen |> -Kette verwenden können.

Tatsächlich könnte die Definition von |> jetzt wahrscheinlich in die geändert werden
Verhalten, nach dem Sie fragen. Ich wäre dafür.

Am Montag, den 27. Januar 2014, benachrichtigt Spencer [email protected]
schrieb:

Einer der Anwendungsfälle, die Menschen zu mögen scheinen, ist die "fließende Schnittstelle". Es ist
Manchmal hilfreich in OOP-APIs, wenn Methoden das Objekt zurückgeben, damit Sie dies tun können
Dinge wie some_obj.move (4, 5) .scale (10) .display ()

Für mich denke ich, dass dies besser als Funktionszusammensetzung ausgedrückt wird, aber
Das |> funktioniert nur mit Argumenten, wenn Sie anon verwenden. Funktionen, zB some_obj
|> x -> bewegen (x, 4, 5) |> x -> skalieren (x, 10) |> anzeigen, was hübsch ist
hässlich.

Eine Option, um diese Art von Dingen zu unterstützen, wäre, wenn |> die LHS als verschoben würde
das erste Argument an die RHS vor der Bewertung, aber dann konnte es nicht sein
implementiert als einfache Funktion wie es jetzt ist.

Eine andere Option wäre eine Art @composed- Makro, das dies hinzufügt
Art des Verhaltens auf den folgenden Ausdruck

Sie können die Verantwortung für die Unterstützung auch auf die Bibliothek verlagern
Designer, wo sie definieren konnten

Funktion verschieben (obj, x, y)
# Verschiebe das Objekt
Ende

move (x, y) = obj -> move (obj, x, y)

Wenn Sie also kein Objekt angeben, wird eine Teilfunktionsanwendung ausgeführt
(durch Rückgabe einer Funktion mit 1 Argument), die Sie dann in a verwenden können
normale |> Kette.

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/5571#issuecomment -33408448
.

ssfrr Ich mag die Art, wie du denkst! Die Funktionszusammensetzung |> war mir nicht bekannt. Ich sehe, dass es kürzlich eine ähnliche Diskussion gegeben hat [https://github.com/JuliaLang/julia/issues/4963].

kmsquire Ich mag die Idee, die aktuelle Funktionszusammensetzung zu erweitern, damit Sie Parameter für die aufrufende Funktion ex angeben können. some_obj |> move(4, 5) |> scale(10) |> display . Native Unterstützung würde einen Abschluss weniger bedeuten, aber was ssfrr vorschlug, ist vorerst ein praktikabler Weg und sollte als zusätzlicher Vorteil auch vorwärtskompatibel mit der erweiterten Funktionskomposition sein, wenn es implementiert wird.

Danke für die prompten Antworten :)

Eigentlich war @ssfrr richtig - es ist nicht möglich, dies als einfache Funktion zu implementieren.

Was Sie wollen, sind Threading-Makros (z. B. http://clojuredocs.org/clojure_core/clojure.core/-%3E). Leider ist @ -> @ - >> @ -? >> in Julia keine brauchbare Syntax.

Ja, ich dachte, dass Infix-Makros eine Möglichkeit sein würden, dies zu implementieren. Ich bin mit Makros nicht vertraut genug, um die Einschränkungen zu kennen.

Ich denke, das funktioniert für @ssfrrs Compose-Makro:

Bearbeiten: Dies könnte etwas klarer sein:

import Base.Meta.isexpr
_ispossiblefn(x) = isa(x, Symbol) || isexpr(x, :call)

function _compose(x)
    if !isa(x, Expr)
        x
    elseif isexpr(x, :call) &&    #
        x.args[1] == :(|>) &&     # check for `expr |> fn`
        length(x.args) == 3 &&    # ==> (|>)(expr, fn)
        _ispossiblefn(x.args[3])  #

        f = _compose(x.args[3])
        arg = _compose(x.args[2])
        if isa(f, Symbol)
            Expr(:call, f, arg) 
        else
            insert!(f.args, 2, arg)
            f
        end
    else
        Expr(x.head, [_compose(y) for y in x.args]...)
    end
end

macro compose(x)
    _compose(x)
end
julia> macroexpand(:(<strong i="11">@compose</strong> x |> f |> g(1) |> h('a',"B",d |> c(fred |> names))))
:(h(g(f(x),1),'a',"B",c(d,names(fred))))

Wenn wir diese |> -Syntax haben wollen, wäre ich sicherlich alles, um sie nützlicher zu machen als jetzt. Es schien immer eine kolossale Verschwendung von Syntax zu sein, nur zuzulassen, dass die Funktion rechts statt links angewendet wird.

+1. Dies ist besonders wichtig, wenn Sie Julia für die Datenanalyse verwenden, wo Sie häufig Datenumwandlungs-Pipelines haben. Insbesondere ist Pandas in Python praktisch zu verwenden, da Sie Dinge wie df.groupby ("etwas"). Aggregate (sum) .std (). Reset_index () schreiben können, was ein Albtraum ist, mit der aktuellen |> -Syntax zu schreiben .

: +1: dafür.

(Ich hatte bereits darüber nachgedacht, die Verwendung des Infix-Operators .. vorzuschlagen ( obj..move(4,5)..scale(10)..display ), aber der Operator |> wird auch nett sein.)

Eine andere Möglichkeit ist das Hinzufügen von syntaktischem Zucker zum Curryen, wie z
f(a,~,b) übersetzt in x->f(a,x,b) . Dann könnte |> seine aktuelle Bedeutung behalten.

Oooh, das wäre eine wirklich schöne Möglichkeit, einen Ausdruck in eine Funktion zu verwandeln.

Möglicherweise so etwas wie Clojures anonyme Funktionsliterale, bei denen #(% + 5) die Abkürzung für x -> x + 5 . Dies verallgemeinert sich auch auf mehrere Argumente mit% 1,% 2 usw., sodass #(myfunc(2, %1, 5, %2) eine Abkürzung für x, y -> myfunc(2, x, 5, y)

Ästhetisch denke ich nicht, dass die Syntax sehr gut in ansonsten sehr lesbare Julia passt, aber ich mag die allgemeine Idee.

Um mein Beispiel oben zu verwenden (und anstelle von% zu @malmauds Tilde zu

some_obj |> move(~, 4, 5) |> scale(~, 10) |> display

das sieht ziemlich gut aus.

Das ist insofern schön, als es dem ersten Argument keine besondere Behandlung gibt. Der Nachteil ist, dass wir auf diese Weise ein Symbol aufnehmen.

Vielleicht ist dies ein weiterer Ort, an dem Sie ein Makro verwenden können, sodass die Ersetzung nur im Kontext des Makros erfolgt.

Wir können dies offensichtlich nicht mit ~ tun, da dies in Julia bereits eine Standardfunktion ist. Scala macht dies mit _ , was wir auch tun könnten, aber es gibt ein erhebliches Problem, herauszufinden, welcher Teil des Ausdrucks die anonyme Funktion ist. Zum Beispiel:

map(f(_,a), v)

Welches bedeutet das?

map(f(x->x,a), v)
map(x->f(x,a), v)
x->map(f(x,a), v)

Sie sind alle gültige Interpretationen. Ich erinnere mich, dass Scala die Typensignaturen von Funktionen verwendet, um dies zu bestimmen, was mir unglücklich erscheint, da es bedeutet, dass Sie Scala nicht wirklich analysieren können, ohne die Typen von allem zu kennen. Wir wollen das nicht (und konnten es auch nicht, wenn wir wollten), daher muss es eine rein syntaktische Regel geben, um zu bestimmen, welche Bedeutung beabsichtigt ist.

Richtig, ich sehe Ihren Standpunkt in der Unklarheit, wie weit Sie gehen sollen. In Clojure wird der gesamte Ausdruck in #(...) sodass er eindeutig ist.

Ist es in Julia idiomatisch, _ als Wert ohne Sorge zu verwenden? Wie x, _ = somfunc() wenn somefunc zwei Werte zurückgibt und Sie nur den ersten wollen?

Um das zu lösen, brauchen wir meiner Meinung nach ein Makro mit einer interpolationsähnlichen Verwendung:

some_obj |> @$(move($, 4, 5)) |> @$(scale($, 10)) |> display

Aber ich denke, es wird an diesem Punkt ziemlich laut und ich glaube nicht, dass @$(move($, 4, 5)) uns etwas über die bestehende Syntax x -> move(x, 4, 5) , die IMO sowohl hübscher als auch expliziter ist.

Ich denke, dies wäre eine gute Anwendung eines Infix-Makros. Wie bei # 4498 könnten wir, wenn eine beliebige Regel Funktionen als Infix definiert, die auch auf Makros angewendet werden, ein Makro @-> oder @|> haben, das das Threading-Verhalten aufweist.

Ja, ich mag die Infix-Makro-Idee, obwohl ein neuer Operator für diese Verwendung eingeführt werden könnte, anstatt ein ganzes System für Inplace-Makros zu haben. Zum Beispiel,
some_obj ||> move($,4,5) ||> scale($, 10) |> disp
oder vielleicht behalten Sie einfach |> aber haben Sie eine Regel, die
x |> f verwandelt sich implizit in x |> f($) :
some_obj |> scale($,10) |> disp

Leute, alles sieht wirklich hässlich aus: |> ||> etc.
Bisher habe ich herausgefunden, dass Julias Syntax so klar ist, dass diese oben diskutierten Dinge im Vergleich zu allem anderen nicht so hübsch aussehen.

In Scala ist es wahrscheinlich das Schlimmste - sie haben so viele Operatoren wie ::,:, <<, >> + :: und so weiter - es macht jeden Code nur hässlich und für einen ohne ein paar Monate Erfahrung in der Verwendung nicht lesbar die Sprache.

Tut mir leid zu hören, dass Ihnen die Vorschläge nicht gefallen, Anton. Es wäre hilfreich, wenn Sie einen alternativen Vorschlag machen würden.

Oh, tut mir leid, ich versuche nicht unfreundlich zu sein. Und ja - Kritiker ohne Vorschläge
sind nutzlos.

Leider bin ich kein Wissenschaftler, der Sprachen konstruiert, also tue ich es einfach nicht
weiß, was ich vorschlagen soll ... nun, außer Methoden zu machen, die optional im Besitz von sind
Objekte wie es in einigen Sprachen ist.

Ich mag den Ausdruck "Wissenschaftler, der Sprachen konstruiert" - er klingt viel grandioser als numerische Programmierer, die Matlab satt haben.

Ich bin der Meinung, dass fast jede Sprache eine Möglichkeit hat, Funktionen zu verketten - entweder durch wiederholte Anwendung von . in OO-Sprachen oder durch spezielle Syntax nur für diesen Zweck in funktionaleren Sprachen (Haskell, Scala, Mathematica usw.). Diese letzteren Sprachen haben auch eine spezielle Syntax für anonyme Funktionsargumente, aber ich glaube nicht, dass Julia wirklich dorthin gehen wird.

Ich werde die Unterstützung für Spencers Vorschlag wiederholen - x |> f(a) in f(x, a) , sehr analog zu der Funktionsweise von do Blöcken (und es bekräftigt ein gemeinsames Thema, das das erste Argument von a Funktion ist in Julia für syntaktische Zuckerzwecke privilegiert). x |> f wird dann als Abkürzung für x |> f() . Es ist einfach, führt keine neuen Operatoren ein, behandelt die überwiegende Mehrheit der Fälle, für die wir eine Funktionsverkettung wünschen, ist abwärtskompatibel und passt zu den vorhandenen Julia-Designprinzipien.

Ich denke auch, dass dies hier der beste Vorschlag ist. Das Hauptproblem besteht darin, dass es anscheinend ausgeschlossen ist, |> für Dinge wie E / A-Umleitung oder andere benutzerdefinierte Zwecke zu definieren.

Zu beachten ist, dass . keine spezielle Funktionsverkettungssyntax ist, aber es funktioniert auf diese Weise, wenn die Funktion auf der linken Seite das gerade geänderte Objekt zurückgibt, was der Bibliotheksentwickler absichtlich tun muss.

Analog dazu kann ein Bibliotheksentwickler in Julia bereits die Verkettung mit |> indem er seine Funktionen von N Argumenten definiert, um eine Funktion von 1 Argument zurückzugeben, wenn N-1 Argumente gegeben werden, wie hier erwähnt

Dies scheint jedoch Probleme zu verursachen, wenn Sie möchten, dass Ihre Funktion eine variable Anzahl von Argumenten unterstützt. Daher wäre es hilfreich, einen Operator zu haben, der die Argumentfüllung ausführen kann.

@ JeffBezanson , es scheint, dass dieser Operator implementiert werden könnte, wenn es eine Möglichkeit gäbe, Infix-Makros zu erstellen. Wissen Sie, ob es ein ideologisches Problem gibt oder ob es einfach nicht umgesetzt wird?

Vor kurzem wurde ~ in einem speziellen Fall behandelt, sodass seine Argumente und Aufrufe zitiert wurden
das Makro @~ standardmäßig. |> könnte dazu gebracht werden, dasselbe zu tun.

Natürlich wird in ein paar Monaten jemand nach <| fragen, um dasselbe zu tun ...

Am Donnerstag, den 6. Februar 2014, benachrichtigt Spencer [email protected]
schrieb:

Nur zur Kenntnis nehmen ,. ist keine spezielle Funktionsverkettungssyntax, aber es passiert
um so zu arbeiten, wenn die Funktion auf der linken Seite das Objekt nur zurückgibt
modifiziert, was der Bibliotheksentwickler tun muss
absichtlich.

Analog kann ein Bibliotheksentwickler in Julia bereits die Verkettung unterstützen
mit |> durch Definieren ihrer Funktionen von N Argumenten, um eine Funktion zurückzugeben
von 1 Argument, wenn N-1 Argumente gegeben werden, wie hier erwähnt : //github.com/JuliaLang/julia/issues/5571#issuecomment -33408448

Das scheint Probleme zu verursachen, wenn Sie Ihre Funktion unterstützen möchten
Variable Anzahl von Argumenten, also mit einem Operator, der ausführen könnte
Das Argument Füllen wäre schön.

@ JeffBezanson https://github.com/JeffBezanson , es scheint, dass dies
Der Operator könnte implementiert werden, wenn es eine Möglichkeit gibt, Infix-Makros zu erstellen. Machst du
Wissen Sie, ob es ein ideologisches Problem gibt oder ob es einfach nicht umgesetzt wird?

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/5571#issuecomment -34374347
.

Richtig, ich würde definitiv nicht wollen, dass dies ein Sonderfall ist. Die Handhabung in Ihrem API-Design ist eigentlich gar nicht so schlecht, und selbst die Einschränkung der variablen Argumente ist kein allzu großes Problem, wenn Sie Typanmerkungen eindeutig definieren müssen.

function move(obj::MyType, x, y, args...)
    # do stuff
    obj
end

move(args...) = obj::MyType -> move(obj, args...)

Ich denke, dieses Verhalten könnte von einem @composable -Makro behandelt werden, das die 2. Deklaration handhaben würde.

Die Idee des Infix-Makros ist für mich attraktiv, wenn sie mit der Deklaration von Infix-Funktionen vereinheitlicht wird, was in # 4498 erläutert wird.

Warum sind Julia-Schöpfer so dagegen, dass Objekte ihre eigenen Methoden enthalten? Wo könnte ich mehr über diese Entscheidung lesen? Welche Gedanken und Theorien stecken hinter dieser Entscheidung?

@meglio Ein nützlicherer Ort für allgemeine Fragen ist die Mailingliste oder das StackOverflow julia-lang -Tag. Weitere Diskussionen zu diesem Thema finden Sie in Stefans Vortrag und in den Archiven der Benutzer und Entwicklerlisten .

Wenn ich mich nur einmische, ist es für mich am intuitivsten, einen Platzhalter durch den zu ersetzen
Wert des vorherigen Ausdrucks in der Reihenfolge der Dinge, die Sie zu komponieren versuchen, ähnlich dem as-> -Makro von clojure. Also das:

<strong i="8">@as</strong> _ begin
    3+3
    f(_,y)
    g(_) * h(_,z)
end

würde erweitert werden um:

g(f(3+3,y)) * h(f(3+3,y),z)

Sie können sich den Ausdruck in der vorherigen Zeile "Dropdown" vorstellen, um das Unterstrichloch in der nächsten Zeile zu füllen.

Ich fing an, ein winziges Etwas wie dieses letzte Quartal in einem Anfall von Aufschub der Finalwoche zu skizzieren.

Wir könnten auch eine Oneliner-Version mit |> :

<strong i="19">@as</strong> _ 3+3 |> f(_,y) |> g(_) * h(_,z)

@porterjamesj , ich mag diese Idee!

Genau; das ist ziemlich nett und hat eine ansprechende Allgemeinheit.
Am 7. Februar 2014, 15:19 Uhr, schrieb "Kevin Squire" [email protected] :

@porterjamesj https://github.com/porterjamesj , ich mag diese Idee!

Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/5571#issuecomment -34497703
.

Ich mag die Idee von @porterjamesj nicht nur, weil es ein Hauch frischer Luft ist, sondern weil sie viel flexibler erscheint als frühere Ideen. Wir sind nicht nur mit dem ersten Argument verheiratet, wir haben die freie Wahl der Zwischenvariablen, und dies scheint auch etwas zu sein, das wir jetzt implementieren können, ohne der Sprache neue Syntax oder Sonderfälle hinzufügen zu müssen.

Beachten Sie, dass wir in Julia, da wir nicht viel vom obj.method(args...) -Muster und stattdessen vom method(obj, args...) -Muster ausführen, in der Regel keine Methoden haben, die die Objekte zurückgeben, mit denen sie für den Express arbeiten Zweck der Methodenverkettung. (Was jQuery macht und was in Javascript fantastisch ist). Wir sparen hier also nicht so viel Tipparbeit, aber um "Pipes" zwischen Funktionen einzurichten, finde ich das wirklich schön.

Angesichts der Tatsache, dass Clojures -> und ->> nur Sonderfälle der oben genannten Fälle sind und ziemlich häufig vorkommen, könnten wir diese wahrscheinlich auch ziemlich einfach implementieren. Obwohl die Frage, wie man sie nennt, etwas schwierig ist. Vielleicht @threadfirst und @threadlast ?

Ich mag die Idee, dass dies auch ein Makro ist.

Ist es nicht besser, wenn die Erweiterung, die dem Beispiel folgt, so etwas wie ist?

tmp = 3+3; tmp = f(tmp); return h(tmp, z)

um mehrere Aufrufe derselben Operation zu vermeiden? (Vielleicht war das schon in @porterjamesjs Idee enthalten)

Ein weiterer Vorschlag: Wäre es möglich, dass das Makro die Verknüpfungen f auf f(_) und f(y) auf f(_,y) ? Vielleicht wird es zu viel sein, aber ich denke, dass wir dann die Option haben, Platzhalter nur bei Bedarf zu verwenden ... (Die Verknüpfungen müssen jedoch nur bei alleinigen Funktionsaufrufen zulässig sein, nicht bei Ausdrücken wie g(_) * h(_,z) oben)

@cdsousa Der Punkt über das Vermeiden von Mehrfachanrufen ist gut. Die Clojure-Implementierung verwendet sequentielle Let-Bindungen, um dies zu erreichen. Ich bin mir nicht sicher, ob wir damit durchkommen können, weil ich nicht genug über die Leistung unserer let weiß.

Verwendet das Makro @as Zeilenumbrüche und => als Teilungspunkte, um zu entscheiden, was der Substitutionsausdruck ist und was ersetzt wird?

let Leistung ist gut; Jetzt kann es so schnell wie eine Variablenzuweisung sein, wenn es möglich ist, und ansonsten auch ziemlich schnell.

@ssfrr in meiner Spielzeugimplementierung filtert nur alle Zeilenumbruch-bezogenen Knoten heraus, die der Parser einfügt (NB, ich verstehe all diese nicht wirklich, es wäre wahrscheinlich gut, eine Dokumentation darüber im Handbuch zu haben) und reduziert dann die Substitution über die Liste der verbleibenden Ausdrücke. Let wäre besser, obwohl ich denke.

@ cdsousa :

Ein weiterer Vorschlag: Wäre es möglich, dass das Makro die Verknüpfungen f auf f(_) und f(y) auf f(_,y)

f bis f(_) macht für mich Sinn. Zum zweiten bin ich der Meinung, dass die explizite Angabe des Standorts besser ist, da vernünftige Leute argumentieren könnten, dass entweder f(_,y) oder f(y,_) natürlicher ist.

Angesichts der Tatsache, dass Clojures -> und ->> nur Sonderfälle der oben genannten Art sind und ziemlich häufig vorkommen, könnten wir diese wahrscheinlich auch ziemlich einfach implementieren. Obwohl die Frage, wie man sie nennt, etwas schwierig ist. Vielleicht @threadfirst und @threadlast ?

Ich denke, wenn Sie die explizite Position mit f(_,y...) oder f(y..., _) angeben, ist der Code durchaus verständlich. Während die zusätzliche Syntax (und die Operatoren) in Clojure sinnvoll sind, stehen uns keine zusätzlichen Operatoren zur Verfügung, und ich denke, die zusätzlichen Makros würden den Code im Allgemeinen weniger klar machen.

Verwendet das Makro @as Zeilenumbrüche und => als Teilungspunkte, um zu entscheiden, was der Substitutionsausdruck ist und was ersetzt wird?

Ich würde es für natürlicher halten, |> als Split-Punkt zu verwenden, da es bereits für Pipelining verwendet wird

Damit Sie wissen, gibt es in Lazy.jl eine Implementierung des Threading-Makros, mit der Sie beispielsweise schreiben können:

@>> range() map(x->x^2) filter(iseven)

Auf der positiven Seite erfordert es keine Sprachänderungen, aber es wird ein bisschen hässlich, wenn Sie mehr als eine Zeile verwenden möchten.

Ich könnte auch @as> in Lazy.jl implementieren, wenn Interesse besteht. Lazy.jl hat jetzt auch ein @as Makro.

Sie können mit Monads.jl auch so etwas tun (obwohl Sie eine Haskell-ähnliche Syntax verwenden) (Hinweis: Es muss aktualisiert werden, um die aktuelle Julia-Syntax zu verwenden). Ich vermute jedoch, dass eine spezielle Version für reines Argument-Threading in der Lage sein sollte, die Leistungsprobleme des allgemeinen Ansatzes zu vermeiden.

Lazy.jl sieht aus wie ein sehr schönes Paket und wird aktiv gepflegt. Gibt es einen zwingenden Grund, warum dies in Base sein muss?

Wie funktioniert die Funktionsverkettung mit Funktionen, die mehrere Werte zurückgeben?
Was wäre das Ergebnis einer Verkettung, z.

function foo(a,b)
    a+b, a*b   # x,y respectively
end

und bar(x,z,y) = x * z - y sein?

Wäre dafür nicht eine Syntax wie bar(_1,z,_2) erforderlich?

Ein weiteres Beispiel:

data = [2.255, 3.755, 6.888, 7.999, 9.001]

Die saubere Art zu schreiben: log(sum(round(data))) ist data|>round|>sum|>log
Aber wenn wir ein Basis-2-Protokoll erstellen und auf 3 Dezimalstellen runden wollten,
dann: wir können nur das erste Formular verwenden:
log(2,sum(round(data,3)))

Aber im Idealfall möchten wir in der Lage sein:
data|>round(_,3)|>sum|>log(2,_)
(o.ä)

Ich habe einen Prototyp dafür gemacht, wie ich vorschlage, dass es funktionieren sollte.
https://github.com/oxinabox/Pipe.jl

Es löst nicht den Punkt von @gregid , aber ich arbeite jetzt daran.
Es wird auch nicht die Notwendigkeit behandelt, die Argumente zu erweitern

Es ähnelt den Threading-Makros Lazy.jl von @ one-more-minute, behält jedoch das Symbol |> für die Lesbarkeit bei (persönliche Präferenz).

Ich werde es vielleicht irgendwann langsam zu einem Paket machen

Eine weitere Option ist:

data |>   x -> round(x,2)  |> sum |>  x -> log(2,x)

Obwohl diese Notation länger als log(2,sum(round(data,2))) hilft sie manchmal bei der Lesbarkeit.

@shashi das ist nicht schlecht, habe nicht daran gedacht,
Ich denke im Allgemeinen zu ausführlich, um leicht lesbar zu sein

https://github.com/oxinabox/Pipe.jl Löst jetzt das Problem von
Wenn Sie jedoch sowohl nach _[1] als auch nach _[2] fragen, erfolgt dies durch mehrere Anrufe bei der Subsitution
Was ich nicht sicher bin, ist das wünschenswerteste Verhalten.

Als Außenseiter denke ich, dass der Pipeline-Betreiber von einer Anpassung der Behandlung durch F # profitieren würde.
Zugegeben, F # hat Currying, aber vielleicht könnte etwas Magie am Backend angewendet werden, damit dies nicht erforderlich ist. Wie bei der Implementierung des Operators und nicht der Kernsprache.

Dies würde dazu führen, dass [1:10] |> map(e -> e^2) zu [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] .

Rückblickend spielte @ssfrr darauf an, aber das Argument obj in ihrem Beispiel würde automatisch map als zweites Argument in meinem Beispiel zugewiesen , wodurch Programmierer nicht mehr ihre Funktionen definieren müssen unterstütze es.

Was schlagen Sie vor, was es bedeutet?

Am 5. Juni 2015, um 17:22 Uhr, schrieb H-225 [email protected] :

Als Außenseiter denke ich, dass eine der besseren Möglichkeiten, dies zu tun, darin besteht, die Behandlung von F # anzupassen.
Zugegeben, F # hat Currying, aber vielleicht könnte etwas Magie am Backend angewendet werden, damit dies nicht erforderlich ist. Wie bei der Implementierung des Operators und nicht der Kernsprache.

Dies würde dazu führen, dass [1:10] |> map (e -> e ^ 2) zu [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] führt.

Persönlich finde ich es schön und klar, ohne zu wortreich zu sein.

Natürlich könnte man result = map (sqr, [1:10]) schreiben, aber warum haben sie überhaupt den Pipeline-Operator?
Vielleicht fehlt mir etwas?

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an.

@StefanKarpinski
Lassen Sie den Bediener grundsätzlich wie folgt arbeiten:

  • x |> y(f) = y(x, f)
  • x |> y(f) = y(f, x)

Möglicherweise haben Sie ein Schnittstellenmuster, bei dem jede Funktion, die mit dem Operator verwendet werden soll, die Daten als erstes oder letztes Argument verwendet, je nachdem, welches der oben genannten Muster als Muster ausgewählt wurde.
Für die Funktion map als Beispiel wäre map entweder map(func, data) oder map(data, func) .

Ist das klarer?

Lazy.jl sieht aus wie ein sehr schönes Paket und wird aktiv gepflegt. Gibt es einen zwingenden Grund, warum dies in Base sein muss?

Ich denke, das ist hier die wichtige Frage.

Der Grund, warum dies in der Basis wünschenswert sein kann, ist zweifach:

1.) Wir möchten vielleicht das Pipelining als julianischen Weg fördern - es kann argumentiert werden, dass es besser lesbar ist
2.) Dinge wie Lazy.jl, FunctionalData.jl und meine eigene Pipe.jl erfordern ein Makro, um den Ausdruck zu verpacken, auf den es einwirken soll - was es weniger lesbar macht.

Ich glaube, die Antwort könnte darin liegen, Infix-Makros zu haben.
Und |> als solches definieren.

Ich bin mir nicht sicher, ob |> (oder ihr Cousin, der do-Block) überhaupt zum Kern gehört.
Es gibt jedoch keine Tools, um sie außerhalb des Parsers zu definieren.

Die Möglichkeit, diese Art von Pipelining-Syntax zu verwenden, scheint sehr gut zu sein. Könnte genau das zu Base hinzugefügt werden, dh x |> y(f) = y(f, x) Teil, den Lazy.j, FunctionalData.jl und Pipe.jl verwenden könnten? : +1:

Nachdem ich mir Code angesehen habe, der die verschiedenen Implementierungen in Paketen verwendet, finde ich ihn persönlich unlesbar und sehr un-julianisch. Das Wortspiel der Pipeline von links nach rechts trägt nicht zur Lesbarkeit bei, sondern hebt Ihren Code lediglich vom Rest des völlig normalen Codes ab, der Klammern für die Funktionsbewertung verwendet. Ich würde eher von einer Syntax abraten, die zu zwei verschiedenen Stilen führt, bei denen Code, der in einem der beiden Stile geschrieben wurde, relativ zu Code, der in dem anderen geschrieben wurde, von innen nach außen und rückwärts aussieht. Warum nicht einfach auf die perfekte Syntax setzen, die wir bereits haben, und dazu ermutigen, die Dinge einheitlicher aussehen zu lassen?

@ Skelman
Persönlich sehe ich es aus einer etwas zweckmäßigen Sicht.
Zugegeben, wenn Sie etwas Einfaches tun, ist es vielleicht nicht notwendig, aber wenn Sie eine Funktion schreiben, die etwas ziemlich Kompliziertes oder Langwieriges bewirkt (auf den ersten Blick: Datenmanipulation, z. B.), dann denke ich, dass hier die Pipeline-Syntax glänzt.

Ich verstehe aber, was du meinst; Es wäre einheitlicher, wenn Sie eine Funktionsaufrufsyntax für alles hätten. Persönlich denke ich, dass es besser ist, es einfacher zu machen, [komplizierten] Code zu schreiben, der leicht zu verstehen ist. Zugegeben, Sie müssen die Syntax und ihre Bedeutung lernen, aber meiner Meinung nach ist |> nicht schwerer zu verstehen als das Aufrufen einer Funktion.

@tkelman Ich würde es aus einem anderen Blickwinkel betrachten. Offensichtlich gibt es Leute, die diesen Programmierstil bevorzugen. Ich kann sehen, dass Sie vielleicht einen konsistenten Stil für den Quellcode von Base haben möchten, aber hier geht es nur darum, die Parser-Unterstützung für ihren bevorzugten Programmierstil für ihre Julia-Anwendungen hinzuzufügen. Wollen Julianer wirklich versuchen, etwas zu diktieren oder auf andere Weise zu unterdrücken, was andere Menschen für nützlich halten?
Ich fand Pipelining zusammen in Unix sehr nützlich. Obwohl ich noch nie eine Programmiersprache verwendet habe, die es in der Sprache ermöglicht, würde ich es zumindest im Zweifelsfall nutzen.

Wir haben |> als Funktions-Piping-Operator, aber es gibt Implementierungsbeschränkungen für die derzeitige Vorgehensweise, die es im Moment ziemlich langsam machen.

Piping eignet sich hervorragend für eine Unix-Shell, in der alles Text ein- und ausgibt. Bei komplizierteren Typen und mehreren Ein- und Ausgängen ist dies nicht so eindeutig. Wir haben also zwei Syntaxen, aber eine macht im MIMO-Fall viel weniger Sinn. Parser-Unterstützung für alternative Programmierstile oder DSLs ist normalerweise nicht erforderlich, da wir leistungsstarke Makros haben.

OK, danke, ich habe mich an @oxinabox 'Kommentar gehalten:

Es gibt jedoch keine Tools, um sie außerhalb des Parsers zu definieren.

Ist klar, was getan werden würde, um die von Ihnen genannten Implementierungsbeschränkungen zu beseitigen?

Einige der früheren Vorschläge könnten möglicherweise umgesetzt werden, indem |> seine Argumente als Makro anstatt als Funktion analysiert. Die frühere Befehlsobjekt-Piping-Bedeutung von |> ist veraltet, so dass dies tatsächlich freigegeben werden kann, um etwas anderes zu tun, kommen 0.5-dev.

Diese Wahl erinnert mich jedoch einiges an die spezielle Analyse von ~ die ich aus Gründen, die ich an anderer Stelle angegeben habe, für einen Fehler halte.

Das Parsen von ~ ist einfach verrückt, es ist eine Funktion in der Basis. Die Verwendung von _ , _1 , _2 erscheint _mehr_ vernünftig (insbesondere, wenn Sie erhöhen, wenn diese Variablen an anderer Stelle im Geltungsbereich definiert sind). Bis wir effizientere anonyme Funktionen haben, scheint dies noch nicht zu funktionieren ...

implementiert, indem |> seine Argumente als Makro anstatt als Funktion analysiert

Es sei denn, Sie tun das!

Das Parsen von ~ ist einfach verrückt, es ist eine Funktion in der Basis

Es ist ein unärer Operator für die bitweise Version. Infix-Binär ~ als Makro analysiert, siehe https://github.com/JuliaLang/julia/issues/4882 , was meiner Meinung nach eine seltsame Verwendung eines ASCII-Operators ist (https://github.com/). JuliaLang / julia / pull / 11102 # issuecomment-98477891).

@ Skelman

Wir haben also zwei Syntaxen, aber eine macht im MIMO-Fall viel weniger Sinn.

3 Syntaxen. So'ne Art.
Pipe in, normaler Funktionsaufruf und Do-Blocks.
Debattierbar sogar 4, da Makros auch eine andere Konvention verwenden.


Für mich,
Die Lesereihenfolge (dh von links nach rechts) == Anwendungsreihenfolge macht für SISO-Funktionsketten viel klarer.

Ich mache viel Code wie (Using iterators.jl und pipe.jl):

  • loaddata(filename) |> filter(s-> 2<=length(s)<=15, _) |> take!(150,_) |> map(eval_embedding, _)
  • results |> get_error_rate(desired_results, _) |> round(_,2)

Für SISO ist es besser (für meine persönliche Präferenz), für MIMO nicht.

Julia scheint sich bereits darauf eingestellt zu haben, dass es mehrere richtige Wege gibt, Dinge zu tun.
Was ich nicht 100% sicher bin, ist eine gute Sache.

Wie gesagt, ich würde gerne Pipe- und Do-Blöcke aus der Hauptsprache entfernen.

Do-Blöcke haben einige sehr hilfreiche Anwendungsfälle, aber es hat mich ein wenig geärgert, dass sie die erste Eingabe als Funktion verwenden müssen, was nicht immer ganz richtig zur Philosophie des Mehrfachversands passt (und Pandas / auch nicht). UFCS im D-Stil mit Postfix data.map(f).sum() , ich weiß, dass es beliebt ist, aber ich glaube nicht, dass es effektiv mit Mehrfachversand kombiniert werden kann.

Piping kann wahrscheinlich ziemlich bald veraltet sein und Paketen zur Verwendung in DSLs wie Ihrer Pipe.jl überlassen werden.

Julia scheint sich bereits darauf eingestellt zu haben, dass es mehrere richtige Wege gibt, Dinge zu tun.
Was ich nicht 100% sicher bin, ist eine gute Sache.

Es hängt mit der Frage zusammen, ob wir einen gemeinschaftsweiten Styleguide konsequent durchsetzen können oder nicht. Bisher haben wir hier nicht viel getan, aber für eine langfristige Interoperabilität, Konsistenz und Lesbarkeit von Paketen denke ich, dass dies mit dem Wachstum der Community immer wichtiger wird. Wenn Sie die einzige Person sind, die jemals Ihren Code lesen wird, gehen Sie verrückt und tun Sie, was Sie wollen. Wenn nicht, ist es aus Gründen der Einheitlichkeit sinnvoll, die Lesbarkeit (Ihrer Meinung nach) etwas schlechter abzuwägen.

@tkelman @oxinabox
Ich habe noch keinen klaren Grund gefunden, warum es nicht in der Sprache oder in den "Kern" -Paketen enthalten sein sollte. [zB: Basis]
Persönlich denke ich, dass es die Antwort sein könnte, |> einem Makro zu machen.
So etwas vielleicht? (Ich bin kein Master Julia Programmierer!)

macro (|>) (x, y::Union(Symbol, Expr))
    if isa(y, Symbol)
        y = Expr(:call, y) # assumes y is callable
    end
    push!(y.args, x)
    return eval(y)
end

Unter Julia v0.3.9 konnte ich es nicht zweimal definieren - einmal mit einem Symbol und einmal mit einem Ausdruck. Mein [begrenztes] Verständnis von Union ist, dass die Verwendung die Leistung beeinträchtigt, daher schätze ich, dass dies in meinem Spielzeug-Beispielcode korrigiert werden muss.

Natürlich gibt es hierfür ein Problem mit der Verwendungssyntax.
Um beispielsweise das Äquivalent von log(2, 10) auszuführen, müssen Sie @|> 10 log(2) schreiben, was hier nicht wünschenswert ist.
Mein Verständnis ist, dass Sie in der Lage sein müssten, Funktionen / Makros sozusagen als "unfixierbar" zu markieren, so dass Sie es dann folgendermaßen schreiben könnten: 10 |> log(2) . (Richtig wenn falsch!)
Erfundenes Beispiel, ich weiß. Ich kann mir momentan keinen guten vorstellen! =)

Es lohnt sich auch, auf einen Bereich hinzuweisen, den ich in meinem Beispiel nicht behandelt habe ...
Also zB:

julia> for e in ([1:10], [11:20] |> zip) println(e) end
(1,11)
(2,12)
(3,13)
(4,14)
(5,15)
(6,16)
(7,17)
(8,18)
(9,19)
(10,20)

Wieder - erfundenes Beispiel, aber hoffentlich bekommen Sie den Punkt!
Ich habe ein bisschen herumgespielt, aber als ich das schrieb, konnte ich mir nicht vorstellen, wie ich das umsetzen sollte.

Weitere Informationen finden Sie unter https://github.com/JuliaLang/julia/issues/554#issuecomment -110091527 und # 11608.

Am 9. Juni 2015, um 21.37 Uhr, schrieb H-225 [email protected] :

Ich habe noch keinen klaren Grund gefunden, warum es nicht in die Sprache aufgenommen werden sollte

Dies ist die falsche mentale Haltung für das Design von Programmiersprachen. Die Frage muss durch "warum?" eher als "warum nicht?" Jedes Feature benötigt einen überzeugenden Grund für seine Aufnahme, und selbst aus gutem Grund sollten Sie lange und gründlich nachdenken, bevor Sie etwas hinzufügen. Kannst du ohne es leben? Gibt es einen anderen Weg, um dasselbe zu erreichen? Gibt es eine andere Variante des Merkmals, die besser und allgemeiner oder orthogonaler zu den vorhandenen Merkmalen wäre? Ich sage nicht, dass diese spezielle Idee nicht passieren könnte, aber es muss eine weitaus bessere Rechtfertigung geben als "warum nicht?" mit ein paar Beispielen, die nicht besser als die normale Syntax sind.

Die Frage muss durch "warum?" eher als "warum nicht?"

+ 1_000_000

Tatsächlich.
Siehe diesen ziemlich bekannten Blog-Beitrag :
Jedes Feature beginnt mit -100 Punkten.
Es muss eine große Verbesserung vorgenommen werden, um die Sprache zu ergänzen.

FWIW, Pyret (http://www.pyret.org/) hat diese genaue Diskussion vor einigen Monaten durchlaufen. Die Sprache unterstützt eine "Kanonenkugel" -Notation, die ursprünglich so funktionierte, wie es die Leute mit |> vorschlagen. In Pyret,

[list: 1, 2, 3, 5] ^ map(add-one) ^ filter(is-prime) ^ sum() ^ ...

Die Kanonenkugel-Notation wollte also keine Argumente zu den Funktionen hinzufügen.

Es dauerte nicht lange, bis sie entschieden, dass diese Syntax zu verwirrend war. Warum wird sum() ohne Argumente aufgerufen? usw. Letztendlich entschieden sie sich für eine elegante Curry-Alternative:

[list: 1, 2, 3, 5] ^ map(_, add-one) ^ filter(_, is-prime) ^ sum() ^ ...

Dies hat den Vorteil, dass es expliziter ist und den Operator ^ zu einer einfachen Funktion vereinfacht.

Ja, das erscheint mir viel vernünftiger. Es ist auch flexibler als Curry.

@StefanKarpinski Ich bin ein wenig verwirrt. Wollten Sie sagen, flexibler als verketten (nicht currying)? Immerhin bestand Pyrets Lösung darin, einfach Curry zu verwenden, was allgemeiner ist als Verketten.

Wenn wir die Syntax |> ein wenig ändern (ich weiß wirklich nicht, wie schwer die Implementierung ist, widerspricht sie möglicherweise | und > ), wir könnte etwas flexibles und lesbares einstellen.

So etwas wie definieren

foo(x,y) = (y,x)
bar(x,y) = x*y

Wir würden haben:

randint(10) |_> log(_,2) |> sum 
(1,2) |_,x>  foo(_,x)   |x,_>   bar(_,2) |_> round(_, 2) |> sum |_> log(_, 2)

Mit anderen Worten, wir hätten einen Operator wie |a,b,c,d> bei dem a , b , c und d die zurückgegebenen Werte von erhalten würden den letzten Ausdruck (in der richtigen Reihenfolge) und verwenden Sie ihn in Platzhaltern innerhalb des nächsten.

Wenn es in |> keine Variablen gibt, würde es so funktionieren, wie es jetzt funktioniert. Wir könnten auch einen neuen Standard festlegen: f(x) |> g(_, 1) würde alle Werte von f(x) und mit dem Platzhalter _ verknüpfen.

@samuela , was ich damit meinte war, dass man beim Currying nur nachfolgende Argumente weglassen kann, während man beim _ -Ansatz alle Argumente weglassen und eine anonyme Funktion erhalten kann. Dh wenn Sie f(x,y) mit Curry gegeben haben, können Sie f(x) tun, um eine Funktion zu erhalten, die y -> f(x,y) ausführt, aber mit Unterstrichen können Sie f(x,_) für dasselbe tun, aber auch mache f(_,y) , um x -> f(x,y) .

Obwohl mir die Unterstrichsyntax gefällt, bin ich mit keiner vorgeschlagenen Antwort auf die Frage zufrieden, wie viel von dem umgebenden Ausdruck darin "erfasst" wird.

Was tun Sie, wenn eine Funktion mehrere Ergebnisse zurückgibt? Müsste es ein Tupel an die Position _ übergeben? Oder könnte es eine Syntax geben, um sie im laufenden Betrieb aufzuteilen? Kann eine dumme Frage sein, wenn ja, entschuldigen Sie!

@StefanKarpinski Ah, ich verstehe was du meinst. Einverstanden.

@ScottPJones Die offensichtliche Antwort ist, ASCII-
http://scrambledeggsontoast.github.io/2014/09/28/needle-announce/

@simonbyrne Das sieht noch schlimmer aus als das Programmieren in Fortran IV auf Lochkarten, wie ich es in meiner fehlenden Jugend getan habe! Ich habe mich nur gefragt, ob eine Syntax wie _1, _2 usw. es erlaubt, eine Mehrfachrückgabe auseinander zu ziehen, oder ist das nur eine dumme Idee von meiner Seite?

@ Simonbyrne Das ist genial. Das als String-Makro zu implementieren, wäre ein erstaunliches GSoC-Projekt.

Warum wird sum () ohne Argumente aufgerufen?

Ich denke, dass das implizite Argument auch eines der verwirrendsten Dinge in do Notation

@simonbyrne Sie glauben nicht, dass dies eindeutig möglich ist? Wenn ja, ist dies meiner Meinung nach eine Unterbrechung wert (die aktuelle Notation do ), wenn sie logischer, allgemeiner und konsistenter gestaltet werden kann.

@simonbyrne Ja, ich stimme vollkommen zu. Ich verstehe die Motivation für die aktuelle do Notation, aber ich bin der festen Überzeugung, dass dies die syntaktische Gymnastik nicht rechtfertigt.

@samuela bezüglich Karte (f, _) vs nur Karte (f). Ich bin damit einverstanden, dass ein magisches Desugaring verwirrend wäre, aber ich denke, dass Karte (f) etwas ist, das existieren sollte. Es würde nicht erfordern und Zucker nur eine einfache Methode hinzufügen, um zu kartieren.
z.B

map(f::Base.Callable) = function(x::Any...) map(f,x...) end

dh map übernimmt eine Funktion und gibt dann eine Funktion zurück, die an iterierbaren Dingen (mehr oder weniger) arbeitet.

Generell denke ich, wir sollten uns eher an Funktionen orientieren, die zusätzliche "Convenience" -Methoden haben, als an eine Art Konvention, bei der |> Daten immer dem ersten Argument (oder ähnlichem) zuordnet.

In der gleichen Weise könnte es eine geben

type Underscore end
_ = Underscore()

und eine allgemeine Konvention, dass Funktionen Methoden haben sollten / könnten, die in bestimmten Argumenten Unterstriche annehmen und dann Funktionen zurückgeben, die weniger Argumente annehmen. Ich bin weniger davon überzeugt, dass dies eine gute Idee wäre, da man für jede Funktion, die n Argumente akzeptiert, 2 ^ n Methoden hinzufügen müsste. Aber es ist ein Ansatz. Ich frage mich, ob es möglich wäre, nicht so viele Methoden explizit hinzufügen zu müssen, sondern sich in die Methodensuche einzuhaken, sodass die entsprechende Funktion zurückgegeben wird, wenn Argumente vom Typ Unterstrich sind.

Wie auch immer, ich denke definitiv, dass es Sinn macht, eine Version von Map und Filter zu haben, die nur einen Callable nimmt und einen Callable zurückgibt. Die Sache mit dem Unterstrich kann funktionieren oder nicht.

@patrickthebold
Ich würde mir vorstellen, dass x |> map(f, _) => x |> map(f, Underscore()) => x |> map(f, x) , wie Sie vorschlagen, der einfachste Weg wäre, map(f, _) zu implementieren, oder? - Ist _ nur eine spezielle Entität, für die Sie programmieren würden?
.
Ich bin mir jedoch nicht sicher, ob dies besser wäre, als es automatisch von Julia ableiten zu lassen - vermutlich unter Verwendung der Syntax |> -, anstatt es selbst programmieren zu müssen.

Auch in Bezug auf Ihren Vorschlag für map - ich mag es irgendwie. In der Tat wäre das für die aktuellen |> ziemlich praktisch. Ich stelle mir jedoch vor, es wäre einfacher, stattdessen einfach die automatische Inferenz von x |> map(f, _) => x |> map(f, x) zu implementieren.

@StefanKarpinski Sinnvoll . Hatte nicht so darüber nachgedacht.

Nichts, was ich sagte, würde in irgendeiner Weise an |> gebunden sein. Was ich in Bezug auf _ gemeint habe, wäre zum Beispiel, < als solche Methoden hinzuzufügen:

<(_::Underscore, x) = function(z) z < x end
<(x, _::Underscore) = function(z) x < z end

Aber ich denke wieder, dass dies ein Schmerz wäre, wenn es nicht eine Möglichkeit gäbe, die entsprechenden Methoden automatisch hinzuzufügen.

Auch hier ist die Sache mit den Unterstrichen etwas anderes als das Hinzufügen der Convenience-Methode zur Zuordnung wie oben beschrieben. Ich denke, beide sollten in irgendeiner Form existieren.

@patrickthebold Ein solcher Ansatz mit einem benutzerdefinierten Typ für Unterstrich usw. würde den Programmierer bei der Implementierung von Funktionen erheblich und unnötig belasten. Alle 2 ^ n auflisten müssen

f(_, x, y) = ...
f(x, _, y) = ...
f(_, _, y) = ...
...

wäre sehr nervig, ganz zu schweigen von unelegant.

Außerdem würde Ihr Vorschlag mit map vermutlich eine Problemumgehungssyntax für map(f) mit grundlegenden Funktionen wie map und filter bereitstellen, aber im Allgemeinen leidet er darunter Komplexitätsproblem als manueller Unterstreichungsansatz. Zum Beispiel func_that_has_a_lot_of_args(a, b, c, d, e) Sie für

func_that_has_a_lot_of_args(a, b, c, d, e) = ...
func_that_has_a_lot_of_args(b, c, d, e) = ...
func_that_has_a_lot_of_args(a, b, e) = ...
func_that_has_a_lot_of_args(b, d, e) = ...
func_that_has_a_lot_of_args(a, d) = ...
...

Und selbst wenn Sie dies tun würden, würden Sie beim Aufrufen der Funktion immer noch mit einer absurden Menge an Zweideutigkeiten konfrontiert sein: Bezieht sich func_that_has_a_lot_of_args(x, y, z) auf die Definition, in der x=a,y=b,z=c oder x=b,y=d,z=e usw. ? Julia würde zwischen ihnen mit Informationen zum Laufzeittyp unterscheiden, aber für den Laienprogrammierer, der den Quellcode liest, wäre dies völlig unklar.

Ich denke, der beste Weg, um das Curry-Unterstreichen richtig zu machen, besteht darin, es einfach in die Sprache zu integrieren. Es wäre schließlich eine sehr einfache Änderung des Compilers. Wenn in einer Funktionsanwendung ein Unterstrich angezeigt wird, ziehen Sie ihn einfach heraus, um ein Lambda zu erstellen. Ich habe vor ein paar Wochen angefangen, dies umzusetzen, aber leider glaube ich nicht, dass ich in den nächsten Wochen genug Freizeit haben werde, um es durchzuhalten. Für jemanden, der mit dem Julia-Compiler vertraut ist, würde es wahrscheinlich nicht länger als einen Nachmittag dauern, bis die Dinge funktionieren.

@samuela
Können Sie klarstellen, was Sie unter "Ziehen Sie es heraus, um ein Lambda zu erstellen" verstehen? - Ich bin neugierig. Ich habe mich auch gefragt, wie das umgesetzt werden kann.

@patrickthebold
Ah ich sehe. Vermutlich könnten Sie dann so etwas verwenden: filter(_ < 5, [1:10]) => [1:4] ?
Persönlich würde ich filter(e -> e < 5, [1:10]) leichter zu lesen finden; konsistenter - weniger versteckte Bedeutung, obwohl ich Ihnen zugebe, ist es prägnanter.

Es sei denn, Sie haben ein Beispiel, wo es wirklich leuchtet?

@samuela

Außerdem würde Ihr Vorschlag mit map vermutlich eine Problemumgehungssyntax für map (f) mit grundlegenden Funktionen wie map und filter bereitstellen, aber im Allgemeinen weist er das gleiche Komplexitätsproblem auf wie der manuelle Unterstrich.

Ich habe nicht vorgeschlagen, dies im Allgemeinen zu tun, nur für map und filter und möglicherweise für einige andere Stellen, an denen dies offensichtlich erscheint. Für mich sollte map so funktionieren: Nehmen Sie eine Funktion auf und geben Sie eine Funktion zurück. (Ich bin mir ziemlich sicher, dass Haskell das tut.)

wäre sehr nervig, ganz zu schweigen von unelegant.

Ich denke, wir sind uns einig. Ich hoffe, es gibt eine Möglichkeit, der Sprache etwas hinzuzufügen, um Methodenaufrufe zu verarbeiten, bei denen einige Argumente vom Typ Unterstrich sind. Nach weiteren Überlegungen denke ich, dass es darauf hinausläuft, dass ein Sonderzeichen automatisch zu einem Lambda erweitert wird oder dass ein spezieller Typ automatisch zu einem Lambda erweitert wird. Ich fühle mich so oder so nicht stark. Ich kann Vor- und Nachteile beider Ansätze erkennen.

@ H-225 Ja, der Unterstrich ist nur eine syntaktische Annehmlichkeit. Ich bin mir nicht sicher, wie häufig es ist, aber Scala hat es auf jeden Fall. Persönlich mag ich es, aber ich denke, es ist nur eines dieser Stilsachen.

@ H-225 Nun, in diesem Fall denke ich, dass ein überzeugendes und relevantes Beispiel die Funktionsverkettung wäre. Anstatt schreiben zu müssen

[1, 2, 3, 5]
  |> x -> map(addone, x)
  |> x -> filter(isprime, x)
  |> sum
  |> x -> 3 * x
  |> ...

man könnte einfach schreiben

[1, 2, 3, 5]
  |> map(addone, _)
  |> filter(isprime, _)
  |> sum
  |> 3 * _
  |> ...

Ich verwende diese Unterstrichsyntax (oder eine geringfügige Variante) unwissentlich ständig in Sprachen, die sie unterstützen, und erkenne nur, wie hilfreich sie beim Übergang zur Arbeit in Sprachen ist, die sie nicht unterstützen.

Soweit ich weiß, gibt es derzeit mindestens 3,5 Bibliotheken / Ansätze, die versuchen, dieses Problem in Julia zu beheben: Julias eingebaute |> -Funktion, Pipe.jl, Lazy.jl und 0,5 für Julias eingebaute do Notation, die im Geist ähnlich ist. Keine dieser Bibliotheken oder Ansätze zu verprügeln, aber viele von ihnen könnten stark vereinfacht werden, wenn Julia das Unterstreichen von Curry unterstützen würde.

@samuela Wenn Sie mit einer Implementierung dieser Idee spielen

<strong i="7">@p</strong> map [1,2,3,4] addone | filter isprime | sum | times 3 _

Der letzte Teil zeigt, wie die Eingabe in den zweiten Parameter geleitet wird (Standard ist Argument eins. In diesem Fall kann _ weggelassen werden). Feedback sehr geschätzt!


Bearbeiten: Das obige wird einfach umgeschrieben in:

times(3, sum(filter(map([1,2,3,4],addone), isprime)))

Hier werden FunctionalData.map und Filter anstelle von Base.map und Filter verwendet. Der Hauptunterschied ist die Argumentreihenfolge, der zweite Unterschied ist die Indizierungskonvention (siehe Dokumente). In jedem Fall kann Base.map einfach durch Umkehren der Argumentreihenfolge verwendet werden. @p ist eine recht einfache Umschreiberegel (von links nach rechts wird von innen nach außen, plus Unterstützung für einfaches Currying: <strong i="17">@p</strong> map data add 10 | showall wird

showall(map(data, x->add(x,10)))

Hack kann so etwas einführen: https://github.com/facebook/hhvm/issues/6455. Sie verwenden $$ was für Julia vom Tisch ist ( $ ist bereits zu überladen).

FWIW, ich mag Hacks Lösung dafür wirklich.

Ich mag es auch, mein Hauptvorbehalt ist, dass ich immer noch eine Terser-Lambda-Notation mag, die _ für Variablen / Slots verwenden könnte, und es wäre gut sicherzustellen, dass diese nicht in Konflikt stehen.

Könnte man nicht __ ? Was ist die Lambda-Syntax, an die Sie denken? _ -> sqrt(_) ?

Sicher könnten wir. Diese Syntax funktioniert bereits. Es geht eher um eine Syntax, für die der Pfeil nicht erforderlich ist, sodass Sie etwas in der Art von map(_ + 2, v) schreiben können. Das eigentliche Problem besteht darin, wie viel des umgebenden Ausdrucks _ gehört zu.

Hat Mathematica nicht ein ähnliches System für anonyme Argumente? Wie macht
sie behandeln den Umfang der Begrenzung dieser Argumente?
Am Dienstag, 3. November 2015, um 9:09 Uhr Stefan Karpinski [email protected]
schrieb:

Sicher könnten wir. Diese Syntax funktioniert bereits, es geht eher um eine Syntax, die
benötigt den Pfeil nicht, damit Sie etwas entlang der Linien schreiben können
der Karte (_ + 2, v), wobei das eigentliche Problem darin besteht, wie viel von der Umgebung
Ausdruck, zu dem das _ gehört.

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

https://reference.wolfram.com/language/tutorial/PureFunctions.html , angezeigt
Das # -Symbol ist das, woran ich gedacht habe.
Am Dienstag, den 3. November 2015 um 09:34 Uhr schrieb Jonathan Malmaud

Hat Mathematica nicht ein ähnliches System für anonyme Argumente? Wie macht
sie behandeln den Umfang der Begrenzung dieser Argumente?
Am Dienstag, 3. November 2015, um 9:09 Uhr Stefan Karpinski [email protected]
schrieb:

Sicher könnten wir. Diese Syntax funktioniert bereits, es geht eher um eine Syntax, die
benötigt den Pfeil nicht, damit Sie etwas entlang der Linien schreiben können
der Karte (_ + 2, v), wobei das eigentliche Problem darin besteht, wie viel von der Umgebung
Ausdruck, zu dem das _ gehört.

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

Mathematica verwendet & , um es abzugrenzen.

Anstatt etwas so Allgemeines wie eine kürzere Lambda-Syntax zu tun (die einen beliebigen Ausdruck annehmen und eine anonyme Funktion zurückgeben könnte), könnten wir das Trennzeichenproblem umgehen, indem wir die akzeptablen Ausdrücke auf Funktionsaufrufe und die akzeptablen Variablen / Slots auf ganze Parameter beschränken. Dies würde uns eine sehr saubere Curry-Syntax mit mehreren Parametern à la Open Dyln geben . Da _ ganze Parameter ersetzt, kann die Syntax minimal, intuitiv und eindeutig sein. map(_ + 2, _) würde in x -> map(y -> y + 2, x) . Die meisten nicht funktionierenden Aufrufausdrücke, die Sie lambdafy möchten, sind wahrscheinlich länger und liebenswürdiger für -> oder do . Ich denke, der Kompromiss zwischen Benutzerfreundlichkeit und Allgemeinheit wäre es wert.

@durcan , das klingt vielversprechend - kannst du die Regel etwas _ im Argument von map während das zweite den gesamten Ausdruck map verbraucht? Mir ist nicht klar, was "Beschränken der akzeptablen Ausdrücke auf Funktionsaufrufe" bedeutet oder was "Beschränken akzeptabler Variablen / Slots auf ganze Parameter" bedeutet ...

Ok, ich glaube, ich verstehe die Regel, nachdem ich einige dieser Dylan-Dokumentationen gelesen habe, aber ich muss mich fragen, ob map(_ + 2, v) funktioniert, aber map(2*_ + 2, v) nicht funktioniert.

Es gibt auch das sehr pingelige Geschäft, dass dies bedeutet, dass _ + 2 + _ (x,y) -> x + 2 + y während _ ⊕ 2 ⊕ _ y -> (x -> x + 2) + y weil + und * sind die einzigen Operatoren, die derzeit als Funktionsaufrufe mit mehreren Argumenten anstatt als paarweise assoziative Operationen analysiert werden. Nun könnte argumentiert werden, dass diese Inkonsistenz behoben werden sollte, obwohl dies zu bedeuten scheint, dass der Parser eine Meinung darüber hat, welche Operatoren assoziativ sind und welche nicht, was schlecht erscheint. Ich würde jedoch argumentieren, dass jedes Schema, bei dem bekannt sein muss, ob der Parser a + b + c als einzelnen Funktionsaufruf oder als verschachtelten Aufruf analysiert, etwas fragwürdig sein kann. Vielleicht sollte die Infix-Notation speziell behandelt werden? Aber nein, das fühlt sich auch faul an.

Ja, Sie haben den Kompromiss getroffen. Einerseits sind lange Zeichenfolgen von Operatoren die Syntax, die bei einigen unserer aktuellen Parsing-Optionen am schwierigsten ist (obwohl es schwierig ist, die Semantik eines Sprachfeatures abhängig von der aktuellen Semantik der Sprache zu bemängeln). Auf der anderen Seite zeichnet sich eine lange Reihe von Funktionsaufrufen aus. Zum Beispiel könnten wir Ihr Problem wie folgt umschreiben:

2*_ |> 2+_ |> map(_, v)

Wie auch immer, ich denke nicht, dass das Infix-Problem einer sauberen Option ohne Trennzeichen im Wege stehen sollte. Es würde wirklich bei den meisten normalen Funktionsaufrufen helfen, was eine Art Problem darstellt. Wenn Sie möchten, können Sie ein optionales Trennzeichen verwenden, um diese spezielle Mehrdeutigkeit zu lösen (hier stehle ich & für diese Rolle):

_ ⊕ 2 ⊕ _    # y -> (x -> x + 2) + y
_ ⊕ 2 ⊕ _ &  # (y , x) -> x + 2 + y

Dies ist der bisher beste Vorschlag, aber ich bin nicht vollständig verkauft. Es ist ziemlich klar, was passiert, wenn die Funktionsaufrufe explizit sind, aber weniger klar, wenn die Funktionsaufrufe durch die Infix-Syntax impliziert werden.

Ich stelle mir diesen Ansatz eher als flexibles und verallgemeinertes Currying als als kurzes und süßes Lambda vor (und selbst dann können wir mit einem optionalen Trennzeichen so ziemlich den ganzen Weg dorthin gelangen). Ich würde etwas Perfekteres lieben, aber ohne mehr symbolisches Rauschen hinzuzufügen (das Gegenteil dieser Ausgabe), bin ich mir nicht sicher, wie ich dorthin komme.

Ja, ich mag es bis auf das Infix. Dieser Teil kann repariert werden.

Das Currying in der Infix-Position könnte ein Syntaxfehler sein:

map(+(*(2, _), 2), v)      # curry is OK syntax, but obviously not what you wanted
map(2*_ + 2, v)            # ERROR: syntax: infix curry requires delimitation
map(2*_ + 2 &, v)          # this means what we want
map(*(2,_) |> +(_,2), v)   # as would this

Es könnte auch eine Warnung sein, denke ich.

Wenn ich das Currying nenne, finde ich das verwirrend und falsch.

Sicher, dies ist eher eine Teilfunktionsanwendung (die optional zu einem anonym argumentierten Lambda wird), denke ich. Abgesehen von der Benennung irgendwelche Gedanken?

Ich denke in etwa so:

  • Wenn _ alleine als eines der Argumente eines Funktionsaufrufausdrucks angezeigt wird, wird dieser Funktionsaufruf durch einen anonymen Funktionsausdruck ersetzt, der so viele Argumente enthält, wie die Funktion _ Argumente hat, deren Hauptteil der ist Originalausdruck mit _ Argumenten, die der Reihe nach durch Lambda-Argumente ersetzt wurden.
  • Wenn _ anderer Stelle angezeigt wird, wird der umgebende Ausdruck bis einschließlich der Pfeilprioritätsstufe oder umgebender Klammern (jedoch nicht eckige Klammern oder geschweifte Klammern) durch eine anonyme Funktion ersetzt, die so viele Argumente wie _ in diesem Ausdruck, dessen Hauptteil der ursprüngliche Ausdruck ist, wobei _ Instanzen der Reihe nach durch Lambda-Argumente ersetzt werden.

Beispiele:

  • f(_, b)x -> f(x, b)
  • f(a, _)x -> f(a, x)
  • f(_, _)(x, y) -> f(x, y)
  • 2_^2x -> 2x^2
  • 2_^_(x, y) -> 2x^y
  • map(_ + 2, v)map(x -> x + 2, v)
  • map(2_ + 2, v)map(x -> 2x + 2, v)
  • map(abs, _)x -> map(abs, x)
  • map(2_ + 2, _)x -> map(y -> 2y + 2, x)
  • map(2_ - _, v, w)map((x, y) -> 2x - y, v, w)
  • map(2_ - _, v, _)x -> map((y, z) -> 2y - z, v, x)
  • map(2_ - _, _, _)(x, y) -> map((z, w) -> 2z - w, x, y)
  • _x -> x
  • map(_, v)x -> map(x, v)
  • map((_), v)map(x -> x, v)
  • f = _f = x -> x
  • f = 2_f = x -> 2x
  • x -> x^_x -> y -> x^y
  • _ && _(x, y) -> x && y
  • !_ && _(x, y) -> !x && y

Der einzige Ort, an dem dies anfängt, heikel zu werden, sind Bedingungen - diese Beispiele werden einfach komisch.

Das ist immer noch ein bisschen fummelig und prinzipienlos und es gibt Eckfälle, aber ich komme irgendwohin.

Das scheint mir eine wirklich schlechte Idee zu sein, fast die gesamte Syntax in Julia ist ziemlich vertraut, wenn Sie andere Programmiersprachen verwendet haben. Leute, die sich Syntaxzucker wie diesen ansehen, werden keine Ahnung haben, was für den "Vorteil" das Speichern einiger Zeichen bedeutet.

Beispiele, die mich etwas weniger glücklich machen:

  • 2v[_]x -> 2v[x] (gut)
  • 2f(_)2*(x -> f(x)) (nicht so gut)
  • _ ? "true" : "false"(x -> x) ? "true" : "false"
  • _ ? _ : 0(x -> x) ? (y -> y) : 0

Ich denke, hier geht etwas Tieferes vor sich - es gibt eine Vorstellung von syntaktischen Positionen, in denen ein Funktionsobjekt Sinn macht - und Sie möchten auf die nächstgelegene davon expandieren. Die klassische Position, die ein Funktionsobjekt "will", ist ein Argument für eine andere Funktion, aber die rechte Seite einer Zuweisung ist eine andere Stelle, von der gesagt werden kann, dass sie ein Funktionsobjekt "will" (oder vielleicht ist es genauer zu sagen, dass dies vorzuziehen wäre eine Funktion zum Körper einer Funktion machen).

Vielleicht, aber das gleiche Argument könnte (und wurde) für die Do-Block-Syntax vorgebracht, die meiner Meinung nach insgesamt sehr erfolgreich und nützlich war. Dies hängt eng mit einer besseren Syntax für die Vektorisierung zusammen. Es ist auch nicht so beispiellos - Scala verwendet _ auf ähnliche Weise und Mathematica verwendet # ähnlicher Weise.

Ich denke, Sie könnten auch den Fall machen, dass die _unfamiliar_ Wahl von
Mehrfachversand anstelle eines Einzelversand-Punktoperators im Wesentlichen
zwingt die Entscheidung, eine prägnante Syntax für pronominale Argumente zu haben
Stellen Sie die SVO-Reihenfolge wieder her, mit der die Benutzer vertraut sind.

Am Dienstag, den 17. November 2015 um 12:09 Uhr Stefan Karpinski [email protected]
schrieb:

Vielleicht, aber das gleiche Argument konnte (und wurde) über den Do-Block vorgebracht
Die Syntax, die ich insgesamt für sehr erfolgreich und nützlich halte.
Dies hängt eng mit einer besseren Syntax für die Vektorisierung zusammen. Es ist auch nicht
das beispiellose - Scala verwendet _ auf ähnliche Weise und Mathematica verwendet #
ähnlich.

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

Dies gibt es auch in C ++ mit mehreren Bibliothekslösungen in Boost, die _1, _2, _3 als Argumente verwenden (z. B. _1(x, y...) = x , _2(x, y, z...) = y usw.), wobei die Einschränkung darin besteht, dass dies möglich ist Aufruf zB fun(_1) für x -> fun(x) , fun muss explizit mit der Bibliothek kompatibel gemacht werden (normalerweise über einen Makroaufruf, damit fun einen "Lambda-Typ" akzeptiert "als Parameter).

Ich würde diese knappe Lambda-Notation wirklich gerne in Julia haben.
In Bezug auf das Problem des Desugarings von 2f(_) auf 2*(x -> f(x)) : Wäre es sinnvoll, die Regeln wie folgt zu ändern: "Wenn die erste Regel zutrifft, z. B. f(_) , dann erneut Bewerten Sie die Regeln rekursiv, wobei f(_) die Rolle von _ . Dies würde auch zB f(g(_))x -> f(g(x)) , wobei die "Klammerregel" dies leicht zulässt Halten Sie auf dem gewünschten Niveau an, z. B. f((g(_)))f(x->g(x)) .

Ich mag den Namen "knappe Lambda-Notation" sehr dafür. (Viel besser als Curry).

Ich würde die explizite Aussage von _1 , _2 , _3 wirklich bevorzugen, wenn Sie Lambdas mit mehreren Argumenten bestehen. Im Allgemeinen finde ich es oft verwirrend, Variablennamen im selben Bereich wiederzuverwenden ... und _ x und y im selben Ausdruck zu haben, scheint einfach verrückt verwirrend.

Ich habe festgestellt, dass dieselbe knappe Scala _ -Ssyntax ein wenig Verwirrung gestiftet hat (siehe alle Verwendungen von _ in Scala ).

Außerdem möchten Sie oft:

x -> f(x) + g(x)

oder ähnliches, und ich denke, ich wäre überrascht, wenn Folgendes nicht funktioniert hätte:

f(_) + g(_)

Möglicherweise möchten Sie auch die Reihenfolge der Argumente ändern:

x, y -> f(y, x)
f(_2, _1)  # can't do with other suggested _ syntax

Ich denke, es wäre in Ordnung für die Syntax, eine explizite anonyme Argumentnummerierung zuzulassen ( _1 , _2 , _3 ... usw.), Aber das Hauptproblem bleibt bestehen : Wann genau fördern Sie eine teilweise angewendete Funktion für ein knappes Lambda? Und was genau ist der Lambda-Körper? Ich würde wahrscheinlich Fehler machen, wenn ich explizit (mit einem Trennzeichen) bin, anstatt implizit komplexe Werberegeln zu verwenden. Was sollte

foo(_1, _1 + _2  + f(_1, v1) + g(_2, v3), _3 * _2, v2) + g(_4, v4) +
 f(_2, v2) + g(_3, v5) + bar(_1, v6)

genau meinen? Mit einem Trennzeichen (ich werde λ ) sind die Dinge etwas klarer:

λ(foo(_1, λ(_1 + _2)  + λ(f(_1, v1) + g(_2, v3)), _3 * _2, v2) + g(_4, v4)) + 
λ(f(_2, v2) + g(_3, v5) + bar(_1, v6))

Dies ist offensichtlich ein MethodError: + has no method matching +(::Function, ::Function) , aber zumindest kann ich das an der Art und Weise erkennen, wie es geschrieben ist.

Ich denke, @StefanKarpinski könnte etwas

Dies ist definitiv ein Kompromiss zwischen Knappheit und Allgemeinheit und Lesbarkeit. Natürlich macht es keinen Sinn, etwas einzuführen, das weniger knapp ist als die vorhandene Notation -> . Ich denke aber auch, dass sich ein Trennzeichen lohnt.

Vielleicht nicht knapp genug, aber wie wäre es mit einer Präfixversion von -> , die _ Argumente erfasst? Z.B

(-> 2f(_) + 1)

Ich denke, das Präfixformular sollte eine ziemlich niedrige Priorität haben. Dies kann in einigen Fällen tatsächlich dazu führen, dass die Klammern weggelassen werden, z

map(->_ + 1, x)

Im Moment spiele ich mit der Implementierung von https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

Als Makro transformiert dies alle derartigen Vorkommen in der Zeile.
Der schwierige Teil ist die Implementierung von Vorrang.

Ich werde es wahrscheinlich in den nächsten 12 Stunden nicht fertig stellen, weil es hier Zeit ist
(Vielleicht in den nächsten 24, aber ich muss vielleicht weg)
Sobald das erledigt ist, können wir damit spielen.

Eine seltsame, die aus https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665 stammt

  • f(_,_)x,y -> f(x,y) (das ist vernünftig)
  • f(_,2_) → ??

    • f(_,2_)x,y -> f(x,2y) (angemessen)

    • f(_,2_)x-> f(x,y->2y) (was die Regel meiner Meinung nach vorschlägt und was mein Prototyp produziert)

Aber ich bin nicht sicher, ob ich es richtig habe.

Also hier ist mein Prototyp.
http://nbviewer.ipython.org/gist/oxinabox/50a1e17cfb232a7d1908

Tatsächlich scheitert es definitiv an einigen Tests.

Es ist nicht möglich, eine Belichtungsreihe in der aktuellen AST-Schicht in Betracht zu ziehen - sie sind häufig (immer?) Bereits aufgelöst.

Trotzdem ist es genug, um damit zu spielen, denke ich

Einige Regeln von magrittr in R, die nützlich sein könnten:

Wenn eine Kette mit beginnt. ist es eine anonyme Funktion:

. %>% `+`(1)

ist dasselbe wie Funktion (x) x + 1

Es gibt zwei Arten der Verkettung:
1) Verkettung durch Einfügen als erstes Argument sowie an alle Punkte, die erscheinen.
2) Verketten nur mit Punkten, die erscheinen.

Der Standardmodus ist Modus 1. Wenn jedoch ein Punkt als Argument für die verkettete Funktion angezeigt wird, wechselt magrittr für diesen Schritt in der Kette in den Modus 2.

Damit

2 %>% `-`(1) 

ist 2 - 1,

und

1 %>% `-`(2, . )

ist auch 2 - 1

Modus 2 kann auch durch Umschließen in Klammern angegeben werden:

2 %>% { `-`(2, . - 1) }

wäre das gleiche wie 2 - (2 - 1).

Auch nur ein Hinweis, dass das intelligente Umschalten zwischen Modus 1 und Modus 2 das Problem fast vollständig löst, dass Julia nicht sehr konsequent ist, wenn es darum geht, das Argument zu haben, an das sie wahrscheinlich an erster Stelle gekettet würde. Ich habe auch vergessen, dass Klammern die Auswertung eines Codeabschnitts ermöglichen. Hier ist ein Beispiel aus dem Magrittr-Handbuch:

Iris%>%
{
n <- Probe (1:10, Größe = 1)
H <- Kopf (., N)
T <- Schwanz (., N)
rbind (H, T)
}%>%
Zusammenfassung

Dies ist im Moment nur eine halbherzige Idee, aber ich frage mich, ob es eine Möglichkeit gibt, die Probleme mit "knappem Lambda" und "Dummy-Variable" gleichzeitig zu lösen, indem wir den Tupel-Konstruktor so modifizieren, dass ein fehlender Wert a zurückgibt Lambda, das ein Tupel anstelle eines Tupels zurückgibt? (_, 'b', _, 4) würde also (x, y) -> (x, 'b', y, 4) .

Wenn wir dann die Semantik des Funktionsaufrufs subtil so ändern, dass foo(a, b) " foo auf das Tupel (a, b) anwenden" oder wenn das Argument eine Funktion ist, dann wenden Sie foo an das von der Funktion zurückgegebene Tupel ". Dies würde bedeuten, dass foo(_, b, c)(1) apply(foo, ((x) -> (x, b, c))(1)) .

Ich denke, dies löst das Problem der Infix-Notation immer noch nicht, aber ich persönlich würde mich über knappe Lambdas freuen, die nur mit Funktionsaufrufen in Klammern funktionieren. Schließlich kann 1 + _ Bedarf jederzeit umgeschrieben werden +(1, _) .

@jballanc Tupelkonstruktion und Funktionsanwendung sind jedoch zwei recht unterschiedliche Konzepte. Zumindest, wenn mein Verständnis von Julias Semantik nicht ernsthaft fehlerhaft ist.

@samuela Damit habe ich gemeint, dass foo(a, b) foo((a, b)...) . Das heißt, die Argumente für eine Funktion können konzeptionell als Tupel betrachtet werden, selbst wenn das Tupel in der Praxis niemals konstruiert wird.

@Ich habe versucht, diese Diskussion

Ich möchte nur dafür stimmen, dass |> eine Ergänzung zur "Magie" von do . Soweit ich sehen kann, wäre der einfachste Weg, dies zu tun, es so zu lassen

3 |> foo == foo(3) # or foo() instead of just foo, but it would be nice if the parentheses were optional
3 |> foo(1) == foo(1, 3)
3 |> foo(1,2) == foo(1,2,3)

Mit anderen Worten, a |> f(x) macht mit dem _last_-Argument das, was f(x) do; a; end mit dem _first_ macht. Dies würde es sofort kompatibel machen mit map , filter , all , any et. al., ohne die Komplexität des Scoping von _ -Parametern hinzuzufügen und angesichts der bereits vorhandenen do -Syntax denke ich nicht, dass dies eine unangemessene konzeptionelle Belastung für die Leser des Codes darstellt.

Meine Hauptmotivation für die Verwendung eines solchen Rohrbetreibers sind Sammelpipelines (siehe Nr. 15612), die meiner Meinung nach ein unglaublich leistungsfähiges Konstrukt sind und in vielen Sprachen an Boden gewinnen (was darauf hinweist, dass es sowohl eine Funktion ist, die die Leute wollen, als auch eine Sie werden verstehen).

Das ist das Makro @>> von https://github.com/MikeInnes/Lazy.jl.

@malmaud Schön! Mir gefällt, dass dies bereits möglich ist: D.

Der Unterschied in der Lesbarkeit zwischen diesen beiden Varianten ist jedoch sehr groß:

# from Lazy.jl
@> x g f(y, z)

# if this became a first-class feature of |>
x |> g |> f(y, z)

Ich denke, das Hauptproblem bei der Lesbarkeit besteht darin, dass es keine visuellen Hinweise gibt, wo die Grenzen zwischen Ausdrücken liegen - die Leerzeichen in x g und g f(x, beeinflussen das Verhalten des Codes erheblich, aber das Leerzeichen in f(x, y) wird nicht.

Wie machbar ist es, dieses Verhalten in 0,5 zu |> hinzuzufügen, da @>> bereits existiert?

(Ich möchte nicht zu viel Energie für die Notation des Betreibers aufwenden, also lehnen Sie diesen Vorschlag nicht nur in Bezug auf die Notation ab. Wir können jedoch feststellen, dass "Rohrleitungen" ein natürlicher Begriff dafür sind (vgl der Begriff "Sammlungspipeline"), und dass z. B. F # bereits |> dafür verwendet, obwohl es dort natürlich eine etwas andere Semantik hat, da F # -Funktionen sich von Julia-Funktionen unterscheiden.)

Ja, sicher stimme ich der Lesbarkeit zu. Es wäre technisch keine Herausforderung, |> verhalten, wie Sie es in 0.5 beschreiben. Es ist nur eine Entwurfsfrage.

In ähnlicher Weise wäre es möglich, Lazy.jls @>> Makro-Analysefunktionen zu verketten, die durch |> verkettet sind.

Hm. Ich werde dann anfangen, an einer PR für Lazy.jl zu arbeiten, aber das bedeutet nicht, dass ich möchte, dass dies nicht in 0.5 :) Ich glaube, ich weiß nicht genug über den Julia-Parser und wie man das Verhalten von |> ändert, um dabei zu helfen, es sei denn, ich bekomme ein ziemlich umfangreiches Mentoring.

Ich glaube nicht, dass ich in diesem Thread erwähnt habe, aber ich habe ein anderes Verkettungspaket, ChainMap.jl. Es ersetzt immer _ und wird bedingt in das erste Argument eingefügt. Es wird auch versucht, das Mapping zu integrieren. Siehe https://github.com/bramtayl/ChainMap.jl

Unsere aktuelle Liste verschiedener Bemühungen usw.
Ich denke, es lohnt sich, diese zu überprüfen (idealerweise vor der Stellungnahme, aber w / e).
sie sind alle etwas unterschiedlich.
(Ich versuche chronologisch zu bestellen).

Pakete

Prototypen ohne Paket

Verbunden:


Vielleicht sollte dies in einem der Top-Beiträge bearbeitet werden.

aktualisiert: 2020-04-20

Dies ist eher ein Typensystemexperiment als ein tatsächlicher Versuch, eine Teilanwendung zu implementieren, aber hier ist ein seltsamer: https://gist.github.com/fcard/b48513108a32c13a49a387a3c530f7de

Verwendung:

include("partial_underscore_generated.jl")
using GeneratedPartial

const sub = partialize(-)
sub(_,2)(1) == 1-2
sub(_,_)(1,2) == 1-2
sub(_,__)(1)(2) == 1-2
sub(__,_)(2)(1) == 1-2 #hehehe

# or
<strong i="8">@partialize</strong> 2 Base.:+ # evily inserts methods in + and allows partializations for 2 arguments
(_+2)(1) == 1+2

# fun:
sub(1+_,_)(2,3) == sub(1+2,3)
sub(1+_,__)(2)(3) == sub(1+2,3)
(_(1)+_)(-,1) == -1+1

# lotsafun:
appf(x::Int,y::Int) = x*y
appf(f,x) = f(x)
<strong i="9">@partialize</strong> 2 appf

appf(1+_,3)(2) == appf(1+2,3)
appf(?(1+_),3) == appf(x->(1+x), 3)
appf(?sub(_,2),3) == appf(x->x-2,3) # I made a method *(::typeof(?),::PartialCall), what of it!!?

# wooooooooooooooooooooooooooooooooo
const f = sub
f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,_)))))))))(1,2,3,4,5,6,7,8,9,10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))
f(_,f(__,f(___,f(____,f(_____,f(______,f(_______,f(________,f(_________,__________)))))))))(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))

# this answers Stefan's concern (which inspired me to make this hack in the first place)
#
#    const pmap = partialize(map)
#    map(f(_,a),   v) == map(x->f(x,a), v)
#    pmap(?f(_,a), v) == map(x->f(x,a), v)
#    pmap(f(_,a),  v) == x->map(f(x,a), v)
#
# it adds a few other issues, of course...


Sicherlich kein Implementierungsvorschlag, aber ich finde es lustig, damit zu spielen, und vielleicht kann jemand eine halbe gute Idee daraus machen.

PS Ich habe vergessen zu erwähnen, dass @partialize auch mit ganzzahligen Array-Literalen und -Bereichen funktioniert:

<strong i="15">@partialize</strong> 2:3 Base.:- # partialized for 2 and 3 arguments!

(_-_-_)(1,2,3) == -4
(_-_+_)(1,2,3) == +2

OK, ich habe über die Funktionszusammensetzung nachgedacht, und obwohl IMO im SISO-Fall klar ist, habe ich wohl darüber nachgedacht, wie ich Julias MISO (Quasi-MIMO?) In kleinen verketteten Codeblöcken verwende.

Ich mag die REPL. Viel. Es ist süß, es ist cool, es lässt dich genauso experimentieren wie MATLAB oder Python oder die Shell. Ich nehme gerne Code aus einem Paket oder einer Datei und kopiere ihn in die REPL, sogar in mehrere Codezeilen. Die REPL wertet das in jeder Zeile aus und zeigt mir, was los ist.

Außerdem wird nach jedem Ausdruck ans zurückgegeben / definiert. Jeder MATLAB-Benutzer weiß es (obwohl dies zu diesem Zeitpunkt ein schlechtes Argument ist!). Wahrscheinlich haben die meisten Julia-Benutzer es schon einmal gesehen / benutzt. Ich benutze ans in den seltsamen Situationen, in denen ich mit etwas Stückweise spiele, und stelle fest, dass ich dem, was ich oben geschrieben habe, einen weiteren Schritt hinzufügen möchte. Ich mag es nicht, dass die Verwendung etwas destruktiv ist, daher vermeide ich es, wenn möglich, aber jeder Vorschlag hier befasst sich mit den Rücklaufzeiten von nur einem Kompositionsschritt.

Für mich ist _ magisch nur _odd_, aber ich verstehe, dass viele Leute anderer Meinung sein können. Wenn ich also Code aus Paketen in die REPL kopieren und einfügen möchte und sehe, wie er ausgeführt wird, und wenn ich eine Syntax möchte, die nicht magisch erscheint, dann könnte ich Folgendes vorschlagen:

<strong i="12">@repl_compose</strong> begin
   sin(x)
   ans + 1
   sqrt(ans)
end

Wenn die Funktion mehrere Ausgaben zurückgibt, kann ich in der nächsten Zeile ans[1] , ans[2] usw. einfügen. Es passt genau zum einstufigen Kompositionsmodell, Julias MISO-Modell, und es ist bereits eine sehr standardmäßige Julia-Syntax, nur nicht in Dateien.

Das Makro ist einfach zu implementieren - konvertieren Sie einfach Expr(:block, exprs...) in Expr(:block, map(expr -> :(ans = $expr), exprs) (zu Beginn auch let ans , und möglicherweise gibt es eine Version, die eine anoymöse Funktion erstellt, die eine Eingabe oder so?). Es wäre nicht nötig, in einer Basis zu leben (obwohl die REPL in Julia eingebaut ist, und das passt irgendwie dazu).

Wie auch immer, nur meine Perspektive! Dies war ein langer Thread, den ich lange nicht mehr angeschaut habe!

Es gibt auch ans nach jedem Ausdruck zurück / definiert ans. Jeder MATLAB-Benutzer weiß es (obwohl dies zu diesem Zeitpunkt ein schlechtes Argument ist!).

Das andere Argument ist, dass, wenn _ in der Funktionsverkettung verwendet wird, die REPL auch _ anstelle von ans (für mich würde dies ausreichen, um sie zu entfernen die Magie").

Es gibt eine ganze Reihe von Präzedenzfällen für die Verwendung von _ als "it-Wert" in Sprachen. Dies steht natürlich im Widerspruch zu der vorgeschlagenen Idee, _ als Namen zu verwenden, der Zuweisungen verwirft, und für terser Lambdas.

Ich bin mir ziemlich sicher, dass dies irgendwo in Lazy.jl als <strong i="5">@as</strong> and begin ...

Die Idee, . für die Verkettung zu verwenden, wurde zu Beginn der Konversation verworfen, hat jedoch eine lange Geschichte der Nützlichkeit und würde die Lernkurve für Anwender aus anderen Sprachen minimieren. Der Grund, warum es mir wichtig ist, ist, weil

type Track
  hit::Array{Hit}
end
type Event
  track::Array{Track}
end

event.track[12].hit[43]

gibt mir den 43. Treffer des 12. Tracks eines Events, wenn track und hit einfache Arrays sind, also

event.getTrack(12).getHit(43)

sollte mir das gleiche geben, wenn sie dynamisch bedient werden müssen. Ich möchte nicht sagen müssen

getHit(getTrack(event, 12), 43)

Es wird schlimmer, je tiefer du gehst. Da es sich um einfache Funktionen handelt, ist das Argument breiter als das der Funktionsverkettung (a la Spark).

Ich schreibe das jetzt, weil ich gerade etwas über Rusts Eigenschaften gelernt structs (Julia type ), aber dann haben sie auch impl zum Binden von Funktionen an den Namen eines struct . Soweit ich das beurteilen kann, handelt es sich um reinen syntaktischen Zucker, der jedoch die oben beschriebene Punktnotation zulässt:

impl Event {
  fn getTrack(&self, num: i32) -> Track {
    self.track[num]
  }
}

impl Track {
  fn getHit(&self, num: i32) -> Track {
    self.track[num]
  }
}

was in Julia sein könnte

impl Event
  function getTrack(self::Event, num::Int)
    self.track[num]
  end
end

impl Track
  function getHit(self::Track, num::Int)
    self.hit[num]
  end
end

Die oben vorgeschlagene Syntax interpretiert self : Es ist nur ein Funktionsargument, daher sollte es keine Konflikte mit Mehrfachversand geben. Wenn Sie eine minimale Interpretation von self vornehmen möchten, können Sie den Typ des ersten Arguments implizit festlegen, damit der Benutzer nicht ::Event und ::Track in jeder Funktion, aber eine nette Sache, keine Interpretation zu machen, ist, dass "statische Methoden" nur Funktionen in impl , die nicht self . (Rust verwendet sie für new Fabriken.)

Im Gegensatz zu Rust hat Julia eine Hierarchie auf types . Es könnte auch eine ähnliche Hierarchie für impls , um Codeduplizierungen zu vermeiden. Ein Standard-OOP könnte erstellt werden, indem die Datenhierarchien type und Methode impl genau gleich gemacht werden. Diese strikte Spiegelung ist jedoch nicht erforderlich und in einigen Fällen unerwünscht.

Dies hat einen entscheidenden Punkt: Angenommen, ich habe meine Funktionen track und hit in impl , anstatt getTrack und getHit . so dass sie mit den Arrays track und hit in den type Konflikt gerieten. Würde event.track das Array oder die Funktion zurückgeben? Wenn Sie es sofort als Funktion verwenden, kann dies zur Unterscheidung beitragen, aber types kann auch Function Objekte enthalten. Wenden Sie vielleicht einfach eine pauschale Regel an: Überprüfen Sie nach dem Punkt zuerst die entsprechenden impl und dann die entsprechenden type ?

Um zu vermeiden, dass zwei "Pakete" für das konzeptionell gleiche Objekt ( type und impl ) vorhanden sind, wie wäre es damit:

function Event.getTrack(self, num::Int)
  self.track[num]
end

um die Funktion getTrack an Instanzen vom Typ Event zu binden, so dass

myEvent.getTrack(12)

liefert den gleichen Bytecode wie die Funktion, die auf (myEvent, 12) angewendet wird?

Neu ist die Syntax typename-dot-functionname nach dem Schlüsselwort function und deren Interpretation. Dies würde weiterhin einen Mehrfachversand ermöglichen, ein Python-ähnliches self wenn das erste Argument mit dem Typ identisch ist, an den es gebunden ist (oder wie oben implizit belassen wird), und es ermöglicht eine "statische Methode", wenn Das erste Argument ist nicht vorhanden oder anders geschrieben als der Typ, an den es gebunden ist.

@jpivarski Gibt es einen Grund, warum Sie der Meinung sind, dass die Erstellen von so etwas wie do aber für das letzte Argument , das von einer Piping-Syntax (z. B. |> ) unterstützt wird, der beste Weg wäre:

event |> getTrack(12) |> getHit(43)

Der Hauptgrund, warum ich sehe, dass so etwas wie Rusts Ansatz besser sein könnte, ist, dass er die linke Seite effektiv als Namespace für Funktionen verwendet, sodass Sie möglicherweise Dinge wie parser.parse ohne mit dem vorhandenen in Konflikt zu geraten Julia Base.parse Funktion. Ich würde es befürworten, sowohl den Rust-Vorschlag als auch Rohrleitungen im Hack-Stil bereitzustellen.

@tlycken Das ist jedoch eine mehrdeutige Syntax, abhängig von der Priorität.
Das Erinnern an die Präzision von |> vs call kann verwirrend sein, da es keine wirklichen Hinweise gibt.
(Einige der anderen vorgeschlagenen Optionen werden auch nicht vorgeschlagen.)

Erwägen

foo(a,b) = a+b
foo(a) = b -> a-b

2 |> foo(10) == 12   #Pipe Precedence > Call Precedence 
2 |> foo(10) == 8     #Pipe Precedence < Call Precedence   

@oxinabox Ich 2 |> foo(10) Desugars zu foo(10, 2) ähnlich wie foo(10) do x; bar(x); end Desugars zu foo(x -> bar(x), 10) . Dies impliziert, dass Pipe Vorrang vor Anruf hat (was meiner Meinung nach sowieso am sinnvollsten ist).

Nur in Bezug auf die Syntax ist . weniger visuell aufdringlich und sicherlich mehr Standard als |> . Ich kann eine fließende Funktionskette schreiben, die durch . ohne Leerzeichen (jeweils ein Zeichen) getrennt ist, und jeder kann sie lesen. Mit |> müsste ich Leerzeichen (jeweils vier Zeichen) hinzufügen, und für die meisten Programmierer wäre dies ein visueller Speedbump. Die Analogie zu | von Shell-Skripten ist nett, aber aufgrund der > nicht sofort erkennbar.

Lese ich diesen Thread richtig, dass das Argument gegen Punkt ist, dass es nicht eindeutig ist, ob es ein Mitgliedsdatum von type oder eine Mitgliedsfunktion von impl (mein erster Vorschlag) oder der Funktion erhalten soll Namespace (mein zweiter Vorschlag)? Im zweiten Vorschlag müssen Funktionen definiert werden, die in dem von einem type erstellten Funktionsnamespace definiert sind, nachdem type definiert wurde, damit es sich weigern kann, ein Mitgliedsdatum genau dort zu überschatten.

Das Hinzufügen beider Namespace-Punkte (mein zweiter Vorschlag) und |> wäre für mich in Ordnung. Sie unterscheiden sich in Zweck und Wirkung, obwohl sie beide für eine fließende Verkettung verwendet werden können. |> wie oben beschrieben ist jedoch nicht vollständig symmetrisch zu do , da do das Argument erfordert, das es ausfüllt, um eine Funktion zu sein. Wenn Sie event |> getTrack(12) |> getHit(43) sagen, gilt |> für Nichtfunktionen ( Events und Tracks ).

Wenn Sie event |> getTrack(12) |> getHit(43) sagen, gilt |> für Nichtfunktionen ( Events und Tracks ).

Nein, es gilt für die Funktionsbeschwörungen auf der rechten Seite, indem der linke Operand als letztes Argument für den Funktionsaufruf eingefügt wird. event |> getTrack(12) ist getTrack(12, event) wegen dem, was rechts war, nicht wegen dem, was links war.

Dies müsste bedeuten: a) Vorrang vor Funktionsaufrufen (da es sich um ein Umschreiben des Aufrufs handelt) und b) Anwendungsreihenfolge von links nach rechts (um getHit(43, getTrack(12, event)) statt getHit(43, getTrack(12), event) ) .

Aber getTrack's Signatur ist

function getTrack(num::Int, event::Event)

Wenn also event |> getTrack(12) event in getTrack's letztes Argument einfügt, wird ein Event in das zweite Argument eingefügt, kein Function . Ich habe gerade das Äquivalent mit do und dem ersten Argument versucht, und Julia 0.4 hat sich beschwert, dass das Argument eine Funktion sein muss. (Möglicherweise, weil do event end als eine Funktion interpretiert wird, die event , anstatt die event selbst.)

Die Funktionsverkettung scheint mir ein anderes Problem zu sein als das, was rund um die Punktsyntax ( . ) diskutiert wird. Zum Beispiel, @jpivarski , können Sie bereits viel von dem, was Sie von Rust in Julia erwähnen, ohne neue Funktionen erreichen:

type TownCrier
  name::AbstractString
  shout::Function

  function TownCrier(name::AbstractString)
    self = new(name)
    self.shout = () -> "HELLO, $(self.name)!"
    self
  end
end

tc = TownCrier("Josh")
tc.shout()                                #=> "HELLO, Josh!"
tc.name = "Bob"
tc.shout()                                #=> "HELLO, Bob!"

Ohne zu versuchen, das Gespräch zu sehr zu entgleisen, würde ich vorschlagen, dass wir wirklich lösen müssen, wie man in Julia effizientes Funktionscurrying durchführt. Fragen zur Angabe von Positionen für Argumente in einer Funktionskette würden dahinschmelzen, wenn wir eine gute Möglichkeit hätten, Funktionen zu curryen. Darüber hinaus wären Konstruktionen wie die oben genannten sauberer, wenn der Funktionskörper spezifiziert und bei der Konstruktion einfach mit "self" belegt werden könnte.

@andyferris Ich habe Python verwendet und ich mag es wirklich, _ auf das Ergebnis des vorherigen Ausdrucks zu verweisen. Es funktioniert jedoch nicht innerhalb von Funktionen. Es wäre großartig, wenn wir es überall zum Laufen bringen könnten: innerhalb von Startblöcken, Funktionen usw.

Ich denke, dies könnte die Verkettung vollständig ersetzen. Es bleibt keine Unklarheit über die Reihenfolge der Argumente. Zum Beispiel,

begin
    1
    vcat(_, 2)
    vcat(3, _)
end

# [3, 1, 2]

Wie @MikeInnes bereits erwähnt hat, ist dies in Lazy.jl bereits in @_ verfügbar (und obwohl es ursprünglich nicht so funktioniert hat, verwendet ChainMap.jl diese Art der Verkettung jetzt auch).

Hoffentlich könnte dies zumindest innerhalb von Blöcken mit Punktfusion zusammenarbeiten

begin
    [1, 2, 3]
    .+(_, 2)
    .*(_, 2)
    .-(10, _)
end

oder unter Verwendung der Syntax @chain_map

begin
    ~[1, 2, 3]
    +(_, 2)
    *(_, 2)
    -(10, _)
end

Derzeit gibt es eine Möglichkeit zur Funktionsverkettung mit Objekten, wenn die Funktion im Konstruktor definiert ist. Zum Beispiel die Funktion Obj.times:

type Obj
    x
    times::Function
    function Obj(x)
       this = new(x)
       this.times =  (n) -> (this.x *= n; this)
       this
    end
end

>>>Obj(2).times(3)
Obj(6,#3)

Was ist mit der Implementierung von Elementfunktionen (speziellen Funktionen), die außerhalb der Typdefinition definiert sind? Zum Beispiel würde die Funktion Obj.times wie folgt geschrieben werden:

member function times(this::Obj, n)
     this.x *= n
     return this
end

>>>Obj(2).times(3)
Obj(6,#3)

Dabei ist member ein spezielles Schlüsselwort für Mitgliedsfunktionen.
Mitgliedsfunktionen haben Zugriff auf die Objektdaten. Später werden sie mit einem Punkt nach der Objektvariablen aufgerufen.
Die Idee ist, das Verhalten von Funktionen, die in Konstruktoren definiert sind, mithilfe von "Element" -Funktionen zu reproduzieren, die außerhalb der Typdefinition definiert sind.

So etwas wird in Rust mit Methodensyntax gemacht . Es unterscheidet sich konzeptionell von der Funktionsverkettung, obwohl es verwendet werden könnte, um die Verkettung so aussehen zu lassen, wie es in einigen OO-Sprachen der Fall ist. Wahrscheinlich am besten, um die Probleme separat zu behandeln.

Ich habe dies und einige verwandte Themen gelesen. Hier ist mein Vorschlag:

Grundlegende Verkettung :
in1 |> function1
Gleich wie: in1 |> function1(|>)

in2 |> function2(10)
Gleich wie: in2 |> function2(|>,10)

Noch mehr Verkettung:
in1 |> function1 |> function2(10,|>)

Verzweigung und Zusammenführung von Ketten:
Zweimal mit Zweigen verzweigen out1 , out2 :
function1(a) |out1>
function2(a,b) |out2>

Verwenden Sie die Zweige out1 und out2 :
function3(|out1>,|out2>)

Was ist mit Faulheit?
Brauchen wir so etwas wie die function!(mutating_var) Konvention?
Für faule Funktionen könnten wir function?() ...

Durch die ordnungsgemäße Verwendung der Einrückung ist es einfach, Datenabhängigkeiten vom zugehörigen Anrufdiagramm visuell zu verfolgen.

Ich habe gerade mit einem Muster für die Funktionsverkettung mit dem vorhandenen Operator |> herumgespielt. Zum Beispiel diese Definitionen:
`` `` julia

Filter

unveränderlicher MyFilter {F}
flt :: F.
Ende

Funktion (mf :: MyFilter) (Quelle)
Filter (mf.flt, Quelle)
Ende

Funktion Base.filter (flt)
MyFilter (flt)
Ende

Nehmen

unveränderliches MyTake
n :: Int64
Ende

Funktion (mt :: MyTake) (Quelle)
nehmen (Quelle, mt.n)
Ende

Funktion Base.take (n)
MyTake (n)
Ende

Karte

unveränderliche MyMap {F}
f :: F.
Ende

Funktion (mm :: MyMap) (Quelle)
Karte (mm.f, Quelle)
Ende

Funktion Base.map (f)
MyMap (f)
Ende
enable this to work: Julia
1:10 |> filter (i-> i% 2 == 0) |> take (2) |> map (i-> i ^ 2) |> collect
`` Essentially the idea is that functions like filter return a functor if they are called without a source argument, and then these functors all take one argument, namely whatever is "coming" from the left side of the |> . The |> `` kettet dann einfach alle diese Funktoren aneinander.

filter usw. könnte auch nur eine anonyme Funktion zurückgeben, die ein Argument akzeptiert und nicht sicher ist, welche dieser Optionen leistungsfähiger wäre.

In meinem Beispiel überschreibe ich map(f::Any) in Base. Ich verstehe nicht wirklich, was die bestehende Definition von map bewirkt ...

Ich habe mir gerade dieses Muster ausgedacht, und mein etwas flüchtiger Blick zeigte keine Diskussion über so etwas. Was denken die Leute? Könnte das nützlich sein? Können sich die Leute Nachteile davon vorstellen? Wenn dies funktioniert, ist das vorhandene Design möglicherweise tatsächlich flexibel genug, um eine ziemlich umfassende Verkettungsgeschichte zu ermöglichen?

Dies scheint für beliebige Funktionen nicht praktikabel zu sein, nur für diejenigen, für die MyF definiert wurde?

Ja, dies funktioniert nur für Funktionen, die sich anmelden. Natürlich keine sehr allgemeine Lösung und in gewissem Sinne die gleiche Geschichte wie bei der Vektorisierung, aber angesichts der Tatsache, dass nicht alles, was wir uns erhoffen würden, es für 1.0 schafft, könnte dieses Muster eine ganze Reihe von Szenarien ermöglichen, auf die die Leute zurückgreifen mussten Makros jetzt.

Im Wesentlichen besteht die Idee darin, dass Funktionen wie Filter einen Funktor zurückgeben, wenn sie ohne Quellargument aufgerufen werden, und diese Funktoren dann alle ein Argument verwenden, nämlich was auch immer von der linken Seite des |> "kommt".

Dies ist fast genau die Essenz der Wandler von Clojure. Der bemerkenswerte Unterschied besteht darin, dass Clojure Wandler auf dem Konzept der Reduzierstücke aufbaute. Kurz gesagt, jede Funktion, die eine Sammlung bearbeitet, kann in eine "Mapping" -Funktion und eine "Reduktions" -Funktion zerlegt werden (selbst wenn die "Reduktions" -Funktion einfach concat ). Der Vorteil der Darstellung von Sammlungsfunktionen auf diese Weise besteht darin, dass Sie die Ausführung neu anordnen können, sodass alle "Zuordnungen" per Pipeline erstellt werden können (besonders nützlich für große Sammlungen). Wandler sind also nur eine Extraktion dieser "Zuordnungen", die zurückgegeben werden, wenn sie aufgerufen werden, ohne dass eine Sammlung bearbeitet werden muss.

Das muss nicht so kompliziert sein. Funktionen können sich für das Currying mit Verschlüssen entscheiden:

Base.map(f)    = (xs...) -> map(f, xs...)
Base.filter(f) = x -> filter(f, x)
Base.take(n)   = x -> take(x, n)

Dies sollte ein Paket natürlich nicht tun, da es die Bedeutung dieser Methoden für alle Pakete ändert. Und es Stück für Stück so zu machen, ist nicht besonders intuitiv - welche Argumente sollten Vorrang haben?

Ich würde eine syntaktische Lösung für Call-Sites bevorzugen, wie oben beschrieben, bei der f(a, _, b) auf x -> f(a, x, b) gesenkt wird. Es ist jedoch schwierig, wie in der langen Diskussion oben erwähnt.

Das muss nicht so kompliziert sein. Funktionen können sich für das Currying mit Verschlüssen entscheiden

Ja, ich habe bereits oben vorgeschlagen, dass ich mir nicht sicher bin, ob es einen Leistungsunterschied zwischen diesen beiden gibt.

Welche Argumente sollten Vorrang haben?

Ja, und dann haben wir tatsächlich Dinge wie filter und take , wobei wir in einem Fall die Sammlung als erstes und im anderen als letztes Argument haben ... Ich fühle das irgendwie Zumindest für iteratorähnliche Operationen gibt es normalerweise eine offensichtliche Antwort auf diese Frage.

Sobald _ ein verfügbares Spezialsymbol ist

Ja, ich stimme voll und ganz zu, dass es da draußen eine allgemeinere Lösung gibt, und @malmauds könnte es sein.

Es gibt keinen Perf Diff, da Closures im Wesentlichen nur den Code generieren, den Sie ohnehin von Hand geschrieben haben. Aber da Sie nur Curry machen, können Sie eine Funktion schreiben, die das für Sie erledigt ( curry(f, as...) = (bs...) -> f(as..., bs...) ). Das kümmert sich um Karte und Filter; In der Vergangenheit gab es auch Vorschläge zur Implementierung eines curry , der einen Sentinel-Wert wie curry(take, _, 2) implementiert.

Ich bin hierher gekommen, weil ich gerade drei Sprachen lerne: D, Elixir und jetzt Julia. In D gibt es die einheitliche Funktionsaufrufsyntax , wie in Rust, in Elixir haben Sie den Pipe-Operator . Beide implementieren im Grunde die hier vorgeschlagene Art der Funktionsverkettung, und ich mochte diese Funktion in beiden Sprachen sehr, da sie leicht zu verstehen ist, leicht zu implementieren scheint und Code mithilfe von Streams und anderen Arten von Datenpipelines so viel lesbarer macht.

Ich habe bisher nur ein kurzes Tutorial über Julias Syntax gesehen, aber ich habe diese Funktion sofort gegoogelt, weil ich gehofft hatte, Julia würde auch so etwas haben. Ich denke, dies ist eine +1 für diese Feature-Anfrage von meiner Seite.

Hallo Leute,

Bitte erlauben Sie mir, diese Funktionsanforderung +1 zu geben. Dies wird sehr dringend benötigt. Beachten Sie die folgende Scala-Syntax.

Array(1,2,3,4,5)
  .map(x => x+1)
  .filter(x => x > 5)
  .reduce(_ + _)

Personen, die Spark oder andere MapReduce-basierte Big-Data-Tools verwendet haben, sind mit dieser Syntax sehr vertraut und haben auf diese Weise große und komplizierte Jobs geschrieben. Sogar R, vergleichsweise alt, erlaubt Folgendes.

c(1,2,3,4,5) %>%
  {. + 1} %>%
  {.[which(. > 5)]} %>%
  sum

Beachten Sie die clevere Verwendung von Codeblöcken als Ersatz für eine ordnungsgemäße funktionale Programmierung - nicht die schönste, aber leistungsstarke. In Julia kann ich Folgendes tun.

[1,2,3,4,5] |> 
  _ -> map(__ -> __ + 1, _) |>
  _ -> filter(__ -> __ < 5, _) |>
  _ -> reduce(+, _)

Aber das ist schrecklich und hässlich. Wenn wir keinen objektorientierten Code a la Scala haben können, werden Pipe-Operatoren unglaublich wichtig. Die einfachste Lösung besteht darin, dass die Pipe das erste Argument eingibt, es sei denn, ein Platzhalter wie _ wird explizit verwendet. Dies wäre jedoch nur dann sinnvoll, wenn map geändert würde, um die Datenstruktur als erstes Argument und Funktion aufzunehmen als zweite.

Es sollte auch ein Äquivalent zu Scalas Array(1,2,3).map(_ + 1) , um übermäßige _ -> _ + 1 und ähnliche Syntax zu vermeiden. Ich mag die obige Idee, wo [1,2,3] |> map(~ + 1, _) in map(~ -> ~ + 1, [1,2,3]) . Danke fürs schauen.

Für letztere haben wir Sendungen mit kompakter Syntax [1, 2, 3] .+ 1 Es macht ziemlich süchtig. So etwas zur Reduzierung (und vielleicht zum Filtern) wäre wahnsinnig cool, scheint aber eine große Frage zu sein.

Es ist vernünftig zu bemerken, dass sowohl Piping als auch do um das erste Funktionsargument kämpfen.

Ich werde daran erinnern, dass neue Themen in den Thread kommen, die wir haben,
Nicht eins, nicht zwei, sondern FÜNF Pakete, die Erweiterungen der Basis-SISO-Piping-Funktionalität von Julia in Richtung der vorgeschlagenen Syntax bieten.
Siehe Liste unter: https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

Es ist vernünftig zu bemerken, dass sowohl Piping als auch um das erste Funktionsargument kämpfen.

Wenn wir eine zusätzliche Rohrleitungsfunktionalität in der Basis erhalten wollten, war diese Position nicht mit _ usw. markiert.
Dann würde ich denken, es würde der endgültigen Position Argumente hinzufügen, nicht der ersten.
das würde es eher wie "so tun als ob Curry / Teilanwendung" machen

Mein Beitrag oben soll ein einfaches Beispiel sein, um die fraglichen Syntaxprobleme zu veranschaulichen.

In der Realität werden häufig Hunderte von Vorgängen in einer Kette verwendet, von denen viele nicht dem Standard entsprechen. Stellen Sie sich vor, Sie arbeiten mit Big Data in natürlicher Sprache. Sie schreiben eine Folge von Transformationen, die eine Zeichenfolge enthalten, chinesische Zeichen herausfiltern, nach Leerzeichen aufteilen, Wörter wie "the" herausfiltern und jedes Wort über das von Ihrem Web verwendete Black-Box-Software-Tool in eine "standardisierte" Form umwandeln Server, Informationen darüber, wie häufig jedes Wort ist, über ein anderes Black-Box-Tool anhängen, Gewichte über jedes einzelne Wort summieren, 100 andere Operationen usw. usw.

Dies sind Situationen, über die ich nachdenke. Solche Operationen ohne Methodenaufrufe oder Pipes durchzuführen, ist aufgrund der Größe kein Anfänger.

Ich weiß nicht, was das beste Design ist. Ich ermutige einfach alle, die Anwendungsfälle und die ausgefeiltere Syntax zu berücksichtigen, als dies derzeit der Fall ist.

Dies sollte in Juno und Julia 0.6 funktionieren

`` `{julia}
mit LazyCall
@lazy_call_module_methods Basisgenerator
@lazy_call_module_methods Iteratorfilter

mit ChainRecursive
start_chaining ()


```{julia}
[1, 2, 3, 4, 5]
<strong i="14">@unweave</strong> ~it + 1
Base.Generator(it)
<strong i="15">@unweave</strong> ~it < 5
Iterators.filter(it)
reduce(+, it)

Ich habe eine Frage zu einer Syntax, die ich in den Kommentaren zu diesem Thema gesehen habe:
https://stackoverflow.com/questions/44520097/method-chaining-in-julia

@somedadaism , Probleme sind für Probleme und nicht, um Stapelüberlauf-Fragen zu "bewerben". Außerdem sind Julia-Leute auf SO und (noch mehr) auf https://discourse.julialang.org/ sehr aktiv

Meine Güte, unglaublich, wie kompliziert das sein kann. +1 für eine anständige Syntax. Für mich besteht die primäre Verwendung von Rohrleitungen auch in der Arbeit mit Daten (Rahmen). Denken Sie dplyr. Persönlich ist es mir eigentlich egal, ob ich standardmäßig an first / last vorbeigehe, aber ich denke, die meisten Paketentwickler lassen ihre Funktionen Daten als erstes Argument akzeptieren - und was ist mit optionalen Argumenten? +1 für so etwas wie

1 |> sin |> sum(2, _)

Wie bereits erwähnt, ist Lesbarkeit und Einfachheit sehr wichtig. Ich würde nicht auf den gesamten dplyr / tidyverse-Stil verzichten wollen, Dinge für die Datenanalyse zu tun ...

Ich möchte hinzufügen, dass ich die mehrzeilige Syntax des Elixiers auch für den Pipe-Operator sehr nützlich finde.

1
|> sin
|> sum(2)
|> println

Entspricht println(sum(sin(1),2))

Nur um einen Vorschlag in der Javascript-Welt zu beachten. Sie verwenden den Operator ? anstelle von _ oder ~ was wir bereits bedeuten ( _ , um etwas zu ignorieren, und ~ als bitweise nicht oder formular). Da wir derzeit ? wie Javascript verwenden, können wir es auch für den Curry-Platzhalter verwenden.

So sieht ihr Vorschlag aus (er ist in Javascript, aber auch in Julia gültig :)

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

const addTen = add(?, 10); // apply from the right
addTen(2); // 12

// with pipeline
let newScore = player.score
  |> add(7, ?)
  |> clamp(0, 100, ?); // shallow stack, the pipe to `clamp` is the same frame as the pipe to `add`.

const maxGreaterThanZero = Math.max(0, ...);
maxGreaterThanZero(1, 2); // 2
maxGreaterThanZero(-1, -2); // 0

Eine Zusammenfassung, weil ich aus anderen Gründen angefangen habe, eine zu schreiben.
Siehe auch meinen vorherigen Kommentar zu verwandten Paketen .

Jeder Fehler mit _ ist nicht fehlerhaft und kann in 1.x durchgeführt werden, da https://github.com/JuliaLang/julia/pull/20328

Dies alles läuft auf zwei Hauptoptionen hinaus (außer Status Quo).
Beide können (in jeder Hinsicht) mit einem Makro implementiert werden, um die Syntax neu zu schreiben.

Spielen Sie mit _ , um anon-Funktionen zu aktivieren

@StefanKarpinskis Terse Lambdas oder eine ähnliche Syntax, bei der das Vorhandensein eines _ (in einem RHS-Ausdruck) anzeigt, dass dieser gesamte Ausdruck eine anonyme Funktion ist.

  • Dies kann fast von einem Makro erledigt werden .

    • Das einzige, was nicht getan werden kann, ist, dass (_) nicht mit _ identisch ist. Welches ist nur die Identitätsfunktion, also spielt es keine Rolle

    • Dies würde überall gelten und wäre nicht nur bei |> nützlich, sondern auch z. B. beim kompakten Schreiben von Dingen wie map(log(7,_), xs) oder log(7, _).(xs) , um das Protokoll mit der Basis 7 jedes Elements zu erstellen von xs .

    • Ich persönlich bevorzuge dies, wenn wir etwas tun.

Spielen Sie mit |> , um Substitutionen durchzuführen

Zu den Optionen gehören:

  • Lassen Sie es seine RHS so wirken, als ob sie Curry machen

    • Eigentlich denke ich, dass dies nicht funktioniert (obwohl es vielleicht eine nicht fehlerhafte Version gibt, die die Methodentabelle überprüft. Ich denke, das ist stattdessen nur verwirrend).

  • Machen Sie es zu einem besonderen Akt von _ (siehe die obigen Optionen und / oder verschiedene Möglichkeiten, es durch Umschreiben zu fälschen)

    • Eine Möglichkeit, dies zu tun, besteht darin, die Erstellung von Infix-Makros zu ermöglichen, dann könnte man @|>@ schreiben und definieren, wie Sie in Paketen wollen (dies wurde bereits geschlossen, sobald https://github.com/JuliaLang/julia/ Ausgaben / 11608)

    • oder geben Sie ihm diese besonderen Eigenschaften an sich

  • Wir haben Tonnen von Makroimplementierungen, um dies zu tun, wie ich sagte, siehe meine Liste der verwandten Pakete
  • Einige Leute schlagen auch vor, es zu ändern, damit es (im Gegensatz zu allen anderen Operatoren) einen Ausdruck in der Zeile verursachen kann, bevor er nicht endet. SO kannst du schreiben
a
|> f
|>g

Anstelle der aktuellen:

a |>
f |>
g

(Die Implementierung mit einem Makro ist nicht möglich, ohne den Block in Klammern zu setzen. Wenn Sie den Block bereits in Klammern setzen, funktioniert er trotzdem.)

  • Ich persönlich mag diese Vorschläge nicht, da sie |> (einen bereits unbeliebten Betreiber) super magisch machen.

Bearbeiten : Wie @StefanKarpinski weiter unten ausführt , ist dies immer eine echte Veränderung.
Weil jemand auf typeof(|>) <: Function angewiesen sein könnte.
Und diese Änderungen würden es zu einem Element der Sprachsyntax machen.

Bonusoption: Es passiert nie Option: Curry überall hinzufügen # 554

Es ist viel zu spät in der Sprache, um Curry hinzuzufügen.
Es wäre verrückt zu brechen und überall riesige Unklarheiten hinzuzufügen.
Und sei einfach sehr verwirrend.

Ich denke, mit diesen beiden Optionen deckt es im Grunde alles ab, was es wert ist, in Betracht gezogen zu werden.
Ich glaube nicht, dass etwas anderes Aufschlussreiches gesagt wurde (dh nicht "+1 will das" oder Wiederholungen von Mikrovarianten oben).

Ich bin ziemlich versucht, |> in 0.7 zu verwerfen, damit wir es später mit einer nützlicheren und möglicherweise nicht funktionsähnlichen Semantik einführen können, von der ich vermute, dass sie notwendig ist, damit die Rohrleitungen gut funktionieren.

Ich bin ziemlich versucht, |> in 0.7 zu verwerfen, damit wir es später mit einer nützlicheren und möglicherweise nicht funktionsähnlichen Semantik einführen können, von der ich vermute, dass sie notwendig ist, damit die Rohrleitungen gut funktionieren.

Der einzige Fall, der auf dieser Liste auftaucht, ist, wenn |> auf der rechten Seite vorgibt, Curry zu sein.
Entweder um seine Argumente in die erste oder in die letzte letzte Argumentposition (en) oder eine andere feste Position einzufügen (die zweite könnte sinnvoll sein).
Ohne _ als Markierung für das Argument zu verwenden, in das eingefügt werden soll.

Es wurden keine weiteren Vorschläge gemacht, die jemand in diesem Thread vage ernst nahm
Es würde mich wundern, wenn es andere vernünftige und dennoch brechende Definitionen für diese Operation gibt.
das hat noch niemand in den letzten fast 4 Jahren vorgeschlagen.

Jedenfalls wäre es nicht schrecklich, es abzulehnen.
Pakete, die es verwenden, können es weiterhin über eines der Makropakete haben.

Eine andere Idee könnte sein, |> mit dem aktuellen Verhalten beizubehalten und die neue Funktionalität unter einem anderen Namen einzuführen, für den keine Umschalttaste erforderlich ist, z. B. \\ (was nicht der Fall ist) sogar gerade als Operator analysieren). Wir haben einmal auf Slack darüber gesprochen, aber ich denke, die Geschichte ist wahrscheinlich im Sand der Zeit verloren.

Rohrleitungen werden häufig interaktiv verwendet, und die einfache Eingabe des Bedieners wirkt sich darauf aus, wie "leicht" sich die Verwendung anfühlt. Ein einzelnes Zeichen | könnte auch nett sein.

Ein einzelnes Zeichen | könnte auch nett sein.

Interaktiv ja, aber dann reicht es aus, es in .juliarc.jl zu haben (was ich schon lange hatte ;-p)

das erfordert nicht die Verwendung der Umschalttaste

Beachten Sie, dass dies eine stark vom Gebietsschema abhängige Eigenschaft ist. Zum Beispiel hat meine schwedische Tastatur eine Reihe von Zeichen zum Verschieben und (ziemlich schreckliche) AltGr-Kombinationen ausgeliefert, um Platz für weitere drei Buchstaben zu schaffen.

Gibt es eine Tradition, |> für diesen Zweck zu verwenden? [Mathematica] (http://reference.wolfram.com/language/guide/Syntax.html) hat // für die Anwendung der Postfix-Funktion, die in den meisten Tastaturen einfach einzugeben sein sollte und möglicherweise verfügbar ist, falls dies der Fall ist wird nicht bereits für Kommentare (wie in C ++) oder Ganzzahldivision (wie in Python) verwendet.

Etwas mit | hat die nette Verbindung mit Shell-Skripten, obwohl natürlich erwartet würde, dass ein einzelnes | bitweise ODER ist. Wird || für logisches ODER genommen? Was ist mit ||| ? Das dreimalige Eingeben eines schwer erreichbaren Zeichens ist nicht viel schwieriger als das einmalige Eingeben.

Gibt es eine Tradition, |> für diesen Zweck zu verwenden?

Ich glaube, die Tradition von |> leitet sich aus der ML-Sprachfamilie ab. Wenn es um Betreiber geht, haben nur wenige Programmiersprachen-Communities diesen Bereich so erkundet wie die ML / Haskell-Community. Eine kleine Auswahl von Beispielen:

Um die obige Liste zu ergänzen, verwendet R%>% - und obwohl diese Sprache veraltet ist, denke ich, dass ihre Pipe-Funktionalität sehr gut gestaltet ist. Eines der Dinge, die es effektiv machen, ist die geschweifte Klammer-Syntax, mit der man Dinge wie schreiben kann

x %>% { if(. < 5) { a(.) } else { b(.) } }

Das wäre in Julia aufgrund der Verwendung von Endanweisungen etwas ausführlicher. Obwohl mein Beispiel oben abstrakt ist, verwenden viele Leute eine ähnliche Syntax, wenn sie Datenvorverarbeitungscode schreiben. Kann bei der Erörterung eines der aktuellen Vorschläge etwas Ähnliches wie oben erreicht werden - möglicherweise durch Verwendung von Klammern?

Ich würde auch die Verwendung eines Pipe-Operators empfehlen, der einen visuellen Hinweis darauf gibt, dass Argumente weitergeleitet werden, z. B. das Symbol > . Dies ist ein hilfreicher Hinweis für Anfänger und diejenigen, die mit funktionaler Programmierung nicht vertraut sind.

Obwohl die vorgeschlagenen Verwendungen von |> syntaktisch nicht mit der aktuellen typischen Verwendung nicht kompatibel sind, sind sie _kompatibel mit |> als Operator - da die meisten von ihnen das Geben von |> weit mehr beinhalten Macht als eine bloße Infix-Funktion. Auch wenn wir sicher sind , wollen wir behalten x |> f |> g bedeutet g(f(x)) , es als einen normalen Bediener verläßt wahrscheinlich noch weitere Verbesserungen ausschließen. Während |> in einen Nicht-Operator geändert wird, der eine Postfix-Funktionsanwendung ausführt, wird die _typische_ Verwendung für verkettete Funktionsanwendungen möglicherweise nicht unterbrochen, dies ist jedoch weiterhin nicht zulässig, da die _atypische_ Verwendung von |> - irgendetwas unterbrochen wird das hängt davon ab, dass es ein Betreiber ist. Das Unterbrechen der atypischen Verwendung ist immer noch nicht möglich und daher in 1.x-Versionen nicht zulässig. Wenn wir einen der oben genannten Vorschläge mit |> , soweit ich das beurteilen kann, müssen wir die Syntax |> anstelle einer Funktion in 1.0 festlegen.

@StefanKarpinski Macht die Syntax |> im Moment eher eine Funktion als eine Funktion auf dem Tisch? Ist es möglich, es rechtzeitig auf den Tisch zu legen, damit es für 1.0 installiert ist?

@StefanKarpinski Ist die Syntax |> im Moment eher eine Funktion als eine Funktion auf dem Tisch? Ist es möglich, es rechtzeitig auf den Tisch zu legen, damit es für 1.0 installiert ist?

Es liegt auf dem Tisch, es in 0,7 zu ​​verwerfen und es vollständig aus 1.0 zu entfernen.
Bringen Sie es dann einige Zeit während 1.x als Syntaxelement zurück.
Was zu diesem Zeitpunkt eine nicht bahnbrechende Veränderung wäre.

Jemand würde es tun müssen, aber ich denke nicht, dass es eine schrecklich schwierige Änderung ist, also ja, es liegt auf dem Tisch.

Wofür würde |> veraltet sein? Eine Implementierung in Lazy.jl?

x |> f kann auf f(x) veraltet sein.

Wie wäre es, wenn Sie l> aber gleichzeitig ll> einführen, das das gleiche Verhalten wie das aktuelle l> ?

Wenn wir nur die Verwertung ersatzlos durchführen, bleiben Pakete, die sich auf das aktuelle Verhalten stützen, im Wesentlichen ohne eine gute Option. Wenn sie in der Zwischenzeit einen Ausdruck erhalten, der etwas weniger schön ist, können sie mit ihrem aktuellen Design fortfahren, aber wir lassen die Option auf dem Tisch, um in Zukunft eine wirklich gute Lösung für l> zu finden.

Dies wirkt sich stark auf das Ökosystem "Abfrage und Freunde" aus: Ich habe ein System erstellt, das der Pipe-Syntax in der R-Tidyverse ziemlich ähnlich ist. Das Ganze ist ziemlich umfassend: Es umfasst die Datei io für derzeit sieben tabellarische Dateiformate (mit zwei weiteren sehr nahe beieinander), alle Abfrageoperationen (wie dplyr) und das Plotten (nicht weit entfernt, aber ich bin optimistisch, dass wir sie haben können) etwas, das sich wie ggplot anfühlt). Alles baut auf der aktuellen Implementierung von l> ...

Ich sollte sagen, ich bin alle dafür, die Optionen für etwas Besseres für l> auf dem Tisch zu halten. Es funktioniert in Ordnung für das, was ich bisher erstellt habe, aber ich könnte leicht einen besseren Ansatz sehen. Aber nur das Abwerten scheint ein sehr radikaler Schritt zu sein, der den Teppich aus vielen Paketen herausziehen kann.

Die andere Möglichkeit besteht darin, x |> f einer alternativen Syntax für f(x) . Das würde Code brechen, der |> überlastet, aber Code, der |> für die Funktionsverkettung verwendet, weiterarbeiten lassen, während die Dinge für zusätzliche Verbesserungen offen bleiben, solange sie damit kompatibel sind.

Die Alternative wäre, in Zukunft eine neue syntaktische Verkettungssyntax einzuführen, aber es muss sich um einen Syntaxfehler handeln, der an dieser Stelle ziemlich schlank ist.

Die Alternative wäre, in Zukunft eine neue syntaktische Verkettungssyntax einzuführen, aber es muss sich um einen Syntaxfehler handeln, der an dieser Stelle ziemlich schlank ist.

Würde mein Vorschlag von oben das nicht zulassen? Das heißt, machen Sie |> einem Syntaxfehler in Julia 1.0 und machen Sie ||> Äquivalent des heutigen |> . Für Julia 1.0 wäre dies ein kleiner Ärger für Code, der derzeit |> da man auf ||> umsteigen müsste. Aber ich denke, das wäre nicht so schlimm und es könnte vollautomatisiert werden. Sobald jemand eine gute Idee für |> hat, kann sie wieder in die Sprache eingeführt werden. Zu diesem Zeitpunkt würde es sowohl ||> als auch |> , und ich gehe davon aus, dass ||> langsam in den Hintergrund treten würde, wenn jeder anfängt, |> . Und dann, in ein paar Jahren, könnte Julia 2.0 einfach ||> entfernen. Meiner Meinung nach würde dies a) niemandem im Zeitrahmen von julia 1.0 echte Probleme bereiten und b) alle Optionen auf dem Tisch lassen, um schließlich eine wirklich gute Lösung für |> .

|>(x, f) = f(x)
|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) # tuple
|>(x, f, args...) = f(x, args...) # args

x = 1 |> (+, 1, 1) |> (-, 1) |> (*, 2) |> (/, 2) |> (+, 1) |> (*, 2) # tuple
y = 1 |> (+, 1, 1)... |> (-, 1)... |> (*, 2)... |> (/, 2)... |> (+, 1)... |> (*, 2)... # args

Es ist nicht einfach, viele Male zu schreiben, aber von links nach rechts und verwendet kein Makro.

function fibb_tuple(n)
    if n < 3
        return n
    end
    fibb_tuple(n-3) |> (+, fibb_tuple(n-2), fibb_tuple(n-1))
end

function fibb_args(n)
    if n < 3
        return n
    end
    fibb_args(n-3) |> (+, fibb_args(n-2), fibb_args(n-1))...
end

function fibb(n)
    if n < 3
        return n
    end
    fibb(n-3) + fibb(n-2) + fibb(n-1)
end

n = 25

println("fibb_tuple")
<strong i="8">@time</strong> fibb_tuple(1)
println("fibb_args")
<strong i="9">@time</strong> fibb_args(1)
println("fibb")
<strong i="10">@time</strong> fibb(1)

println("tuple")
<strong i="11">@time</strong> fibb_tuple(n)
println("args")
<strong i="12">@time</strong> fibb_args(n)
println("fibb")
<strong i="13">@time</strong> fibb(n)
fibb_tuple
  0.005693 seconds (2.40 k allocations: 135.065 KiB)
fibb_args
  0.003483 seconds (1.06 k allocations: 60.540 KiB)
fibb
  0.002716 seconds (641 allocations: 36.021 KiB)
tuple
  1.331350 seconds (5.41 M allocations: 151.247 MiB, 20.93% gc time)
args
  0.006768 seconds (5 allocations: 176 bytes)
fibb
  0.006165 seconds (5 allocations: 176 bytes)

|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) ist schrecklich.
|>(x, f, args...) = f(x, args...) braucht mehr Buchstaben, aber schnell.

Ich denke, dass das Zulassen von subject |> verb(_, objects) wie Syntax als verb(subject, objects) die Unterstützung von SVO bedeutet (aber Julias Standard ist VSO oder VOS). Julia unterstützt jedoch mutltidipatch, so dass das Thema Themen sein kann. Ich denke, wir sollten (subject1, subject2) |> verb(_, _, object1, object2) ähnliche Syntax als verb(subject1, subject2, object1, object2) zulassen, wenn wir die SVO-Syntax einführen.
Es ist MIMO, wenn es als Pipeline erfasst wird, wie @oxinabox es notiert hat.

Wie wäre es mit (x)f als f(x) ?
(x)f(y) kann sowohl als f(x)(y) als auch als f(y)(x) gelesen werden. Wählen Sie also zuerst die richtige Bewertung:

(x)f # f(x)
(x)f(y) # f(y)(x)
(x)f(y)(z) # f(y)(z)(x)
(x)(y)f(z) # f(z)(y)(x)
(a)(b)f(c)(d) # f(c)(d)(b)(a)
1(2(3, 4), 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
1 <| (2 <| (3, 4), 5 <| (6, 7), 8 <| (9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10)), but 2 <| (3, 4) == 2((3, 4)) so currently emit error
3 |> 2(_, 4) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3)2(_, 4))1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
(3, 4) |> 2(_, _) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3, 4)2)1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))

Dies kann Vararg klar manipulieren.
Aber es wird binäre Operatoren ohne Leerzeichen brechen:

(a + b)+(c + d) # +(c + d)(a + b) == (c + d)(a + b): Error

Alternative Option: Fügen Sie einen weiteren Fall für die Splatting-Syntax hinzu. Habe f ... (x) desugar zu (args ...) -> f (x, args ...)

Dies würde syntaktisch leichtes (manuelles) Currying ermöglichen:

#Basic example:
f(a,b,c,d) = #some definition
f...(a)(b,c,d) == f(a,b,c,d)
f...(a,b)(c,d) == f(a,b,c,d)
f...(a,b,c)(d) == f(a,b,c,d)
f...(a)...(b)(c,d) == f(a,b,c,d) # etc etc

# Use in pipelining:
x |> map...(f) |> g  |> filter...(h) |> sum

Wann hörst du auf zu curryen? Julia-Funktionen haben keine feste Arität.

Julia hat bereits einen Splatting-Operator. Was ich vorschlage, würde genau das gleiche Verhalten haben wie der aktuelle Splat-Operator.

I, e: f ... (x) == (args ...) -> f (x, args ...) ist Zucker für die Herstellung eines Lambda mit Splatting.

Diese Definition gibt Ihnen immer ein Funktionsobjekt. Vermutlich möchten Sie manchmal eine Antwort.

Sie erhalten dies, indem Sie das Objekt am Ende explizit aufrufen. Beachten Sie zum Beispiel das Fehlen von ... vor dem letzten Satz von Klammern in meinem letzten Beispiel f ... (a) ... (b) (c, d) == f (a, b, c, d) .

Sie können das zurückgegebene Funktionsobjekt auch mit |> aufrufen, was es für Piping angenehm macht.

@saolof

Grundlegendes Beispiel:

f (a, b, c, d) = #Einige Definition
f ... (a) (b, c, d) == f (a, b, c, d)
f ... (a, b) (c, d) == f (a, b, c, d)
f ... (a, b, c) (d) == f (a, b, c, d)
f ... (a) ... (b) (c, d) == f (a, b, c, d) # etc etc.

Gute Intuition für die Verwendung von Splat mit Funktionsverkettung, aber in meinem Sinne zu komplex.
Sie haben an einem Punkt der Kette mehrere Anträge gestellt.
Bei der Funktionsverkettung führen Sie eine Anwendung Schritt für Schritt entlang der gesamten Kette durch.

Und @StefanKarpinski ist richtig, Sie wissen nicht, wann Sie aufhören sollen, Funktionen über sich selbst anzuwenden und sie schließlich auf ein skalareres Element anzuwenden.

- (abgeschnitten) -

Sorry, das ist ziemlich sinnlos und unlesbar.
Siehe meine zweite Nachricht unten, um eine klarere Erklärung zu erhalten (ich hoffe).

Angesichts der Funktionsfähigkeit von Julia gefällt mir @saolofs Idee eines Funktions-Curry-Operators sehr gut. Ich verstehe nicht wirklich, woher die semantischen Einwände kommen, da dies anscheinend eine sehr offensichtliche Interpretation hat.

Sie können es sogar bequem von Ihrer eigenen Replik aus prototypisieren:

ctranspose(f) = (a...) -> (b...) -> f(a..., b...)

map'(+)(1:10)

map'(+)'(1:10, 11:20)(21:30)

(+)'(1,2,3)(4,5)

1:10 |> map'(x->x^2) |> filter'(iseven)

Hat ein gutes Gefühl, denke ich.

Bearbeiten: Gefühle wie diese könnten auch der Weg sein, dies weiter zu verallgemeinern. Wenn wir map∘(+, 1:10) schreiben können, können wir map∘(_, 1:10) schreiben, um das Curry-Argument an die erste Stelle zu setzen, und der Curry-Operator bestimmt den Umfang des Lambda und löst das größte Problem für ein solches allgemeines Curry.

Eh, das ist klug, @MikeInnes.

Ich finde es toll, wie sich Julias extreme Dehnbarkeit auch hier zeigt. Die einheitliche Lösung für eine Vielzahl von Anforderungen an die Funktionsverkettung stellt sich als Missbrauch von ctranspose ... heraus.

(Klarstellung: Ich bekomme mit diesem Vorschlag 1:10 |> map'(x->x^2) |> filter'(iseven) , also bin ich 💯% dafür!)

Um es klar auszudrücken, ich denke nicht, dass wir den Operator adjoint dafür missbrauchen sollten, aber es ist ein guter Beweis für das Konzept, dass wir eine präzise Funktions-Curry-Notation haben können.

Vielleicht sollten wir einen neuen Unicode-Operator einführen? http://www.fileformat.info/info/unicode/char/1f35b/index.htm

(Es tut uns leid...)

Ich denke, _s sind immer noch eine viel flexiblere Möglichkeit, Lambdas herzustellen

@bramtayl Ich denke, die Idee in MikeInnes 'Bearbeitung seines Beitrags ist, dass die beiden koexistieren können - eigenständige Unterstriche wie in @stevengj ' s Pull-Anfrage würden funktionieren, eigenständiges Currying wie in Mikes obiger Idee würde funktionieren und die beiden kombinieren würde auch funktionieren und es Ihnen ermöglichen, den Currying-Operator zu verwenden, um den Umfang von _ s darin abzugrenzen.

Ich habe es verstanden

Das unterscheidet es nicht allzu sehr von LazyCall.jl

Im Ernst:

Um es klar zu sagen, ich denke nicht, dass wir den Operator adjoint dafür missbrauchen sollten

Wahrscheinlich eine gute Wahl. Ich möchte jedoch meine Hoffnungen zum Ausdruck bringen, dass eine solche Lösung, wenn sie implementiert wird, einen Operator erhält, der einfach zu tippen ist. Die Möglichkeit, so etwas wie 1:10 |> map'(x->x^2) zu tun, ist wesentlich weniger nützlich, wenn ein beliebiges Zeichen, das ' erfordert, dass ich es in einer Unicode-Tabelle nachschlage (oder einen Editor verwende, der LaTeX-Erweiterungen unterstützt).

Anstatt den adjungierten Operator zu missbrauchen, könnten wir den Splat-Operator wiederverwenden.

  • in einem (linearen) Rohrleitungskontext
  • innen in einem Funktionsaufruf

    • Splat eher vorher als nachher

damit

  • splat kann ein fehlendes Iteratorarg führen

Eine Art Splat hoher Ordnung (mit Anakrusis, wenn dort ein Musiker ist).
In der Hoffnung, dass es die Sprache nicht zu sehr erschüttert.

BEISPIEL

1:10
    |> map(...x->x^2)
    |> filter(...iseven)

BEISPIEL 2

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      map(...i -> a*i/n) |>
      map(...t -> [r*cos(t), r*sin(t)]) 

könnte stehen für

elmap = f -> (s -> map(f,s))

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      elmap(i -> a*i/n) |>
      elmap(t -> [r*cos(t), r*sin(t)]) 

Ich bin mir nicht sicher, ob dies hierher gehört, da sich die Diskussion zu einer fortgeschritteneren / flexibleren Verkettung und Syntax entwickelt hat. Zurück zum Eröffnungsbeitrag scheint eine Funktionsverkettung mit Punktsyntax derzeit mit ein wenig mehr Setup möglich zu sein. Die Syntax ist nur eine Folge der Punktsyntax für Strukturen zusammen mit erstklassigen Funktionen / Abschlüssen.

mutable struct T
    move
    scale
    display
    x
    y
end

function move(x,y)
    t.x=x
    t.y=y
    return t
end
function scale(c)
    t.x*=c
    t.y*=c
    return t
end
function display()
    @printf("(%f,%f)\n",t.x,t.y)
end

function newT(x,y)
    T(move,scale,display,x,y)
end


julia> t=newT(0,0)
T(move, scale, display, 0, 0)

julia> t.move(1,2).scale(3).display()
(3.000000,6.000000)

Die Syntax scheint der herkömmlichen OOP sehr ähnlich zu sein, wobei eine Eigenart von "Klassenmethoden" veränderbar ist. Ich bin mir nicht sicher, welche Auswirkungen dies auf die Leistung hat.

@ivanctong Was Sie beschrieben haben, ähnelt eher einer fließenden Benutzeroberfläche als einer Funktionsverkettung.

Die allgemeinere Lösung des Problems der Funktionsverkettung hätte jedoch den zusätzlichen Vorteil, dass sie auch für fließende Schnittstellen verwendet werden kann. Während es derzeit in Julia durchaus möglich ist, mit struct-Mitgliedern so etwas wie eine fließende Oberfläche zu erstellen, scheint es mir sehr gegen den Geist und die Designästhetik von Julia zu verstoßen.

Die Art und Weise, wie Elixier es tut, wenn der Pipe-Operator immer als erstes Argument auf der linken Seite vorbeikommt und danach zusätzliche Argumente zulässt, war ziemlich nützlich. Ich würde gerne so etwas wie "elixir" |> String.ends_with?("ixir") als erstklassigen Bürger sehen in Julia.

Andere Sprachen definieren es als Uniform Function Call Syntax .
Diese Funktion bietet mehrere Vorteile (siehe Wikipedia). Es wäre schön, wenn Julia sie unterstützen würde.

Gibt es zu diesem Zeitpunkt eine fließende Schnittstelle zu Julia?

Bitte stellen Sie Fragen an das Diskussionsforum des Julia-Diskurses .

In einem Anfall von Hacking (und fragwürdigem Urteilsvermögen!?) Habe ich eine weitere mögliche Lösung für die enge Bindung von Funktionsplatzhaltern geschaffen:

https://github.com/c42f/MagicUnderscores.jl

Wie bereits unter https://github.com/JuliaLang/julia/pull/24990 erwähnt , basiert dies auf der Beobachtung, dass bestimmte Slots einer bestimmten Funktion häufig einen _ Platzhalterausdruck fest binden sollen, und andere locker. MagicUnderscores macht dies für jede benutzerdefinierte Funktion erweiterbar (sehr im Sinne der Broadcast-Maschinerie). So können wir solche Dinge haben wie

julia> <strong i="12">@_</strong> [1,2,3,4] |> filter(_>2, _)
2-element Array{Int64,1}:
 3
 4

julia> <strong i="13">@_</strong> [1,2,3,4] |> filter(_>2, _) |> length
2

"einfach arbeiten". (Mit dem @_ verschwinden offensichtlich, wenn es möglich ist, dies zu einer allgemeinen Lösung zu machen.)

Einige Variationen des @ MikeInnes- Vorschlags scheinen für meine Anforderungen angemessen zu sein (normalerweise lange Filter-, Zuordnungs-, Verkleinerungs-, Aufzählungs-, Zip-Ketten usw. mit der Syntax do ).

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

1:10 |> c(map)() do x
    x^2
end |> c(filter)() do x
    x > 50
end

Dies funktioniert, obwohl ich ' nicht mehr zum Laufen bringen kann. Es ist etwas kürzer als:

1:10 |> x -> map(x) do x
    x^2
end |> x -> filter(x) do x
    x > 50
end

Auch ich denke man könnte es einfach machen

cmap = c(map)
cfilter = c(filter)
cetc = c(etc)
...

1:10 |> cmap() do x
    x^2
end |> cfilter() do x
    x > 50
end |> cetc() do ...

Ab 1.0 müssen Sie adjoint anstelle von ctranspose überladen. Sie können auch tun:

julia> Base.getindex(f::Function, x...) = (y...) -> f(x..., y...)

julia> 1:10 |> map[x -> x^2] |> filter[x -> x>50]
3-element Array{Int64,1}:
  64
  81
 100

Wenn wir apply_type überladen könnten, könnten wir map{x -> x^2} :)

@ MikeInnes Ich habe das gerade gestohlen

Ein später und leicht frivoler Beitrag - wie wäre es, wenn Sie Daten mit einer Kombination aus linken und rechten Curry-Operatoren an einen beliebigen Ort in der Argumentliste weiterleiten:

VERSION==v"0.6.2"
import Base: ctranspose, transpose  
ctranspose(f::Function) = (a...) -> ((b...) -> f(a..., b...))  
 transpose(f::Function) = (a...) -> ((b...) -> f(b..., a...))

"little" |> (*)'''("Mary ")("had ")("a ") |> (*).'(" lamb")

Clojure hat einige nette Threading-Makros . Haben wir diese irgendwo im Julia-Ökosystem?

Clojure hat einige nette Threading-Makros . Haben wir diese irgendwo im Julia-Ökosystem?

https://github.com/MikeInnes/Lazy.jl

Clojure hat einige nette Threading-Makros . Haben wir diese irgendwo im Julia-Ökosystem?

Wir haben mindestens 10 von ihnen.
Ich habe eine Liste weiter oben im Thread gepostet.
https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

Können Sie die Liste so bearbeiten, dass LightQuery anstelle der beiden anderen Pakete von mir verwendet wird?

Da der Operator |> von elixir stammt, lassen Sie sich von einer der Möglichkeiten inspirieren, anonyme Funktionen zu erstellen.
In Elixir können Sie &expr zum Definieren einer neuen anonymen Funktion und &n zum Erfassen von Positionsargumenten verwenden ( &1 ist das erste Argument, &2 ist das zweite, usw.)
In Elixir gibt es zusätzliche Dinge zu schreiben (zum Beispiel benötigen Sie einen Punkt vor der Klammer, um eine anonyme Funktion &(&1 + 1).(10) aufzurufen).

Aber so könnte es in Julia aussehen

&(&1 * 10)        # same as: v -> v * 10
&(&2 + 2*&5)      # same as: (_, x, _, _, y) -> x + 2*y
&map(sqrt, &1)    # same as: v -> map(sqtr, v)

So können wir den Operator |> besser verwenden

1:9 |> &map(&1) do x
  x^2
end |> &filter(&1) do x
  x in 25:50
end

Anstatt von

1:9 |> v -> map(v) do x
  x^2
end |> v -> filter(v) do x
  x in 25:50
end

Beachten Sie, dass Sie Zeile 2 und 3 durch .|> &(&1^2) oder .|> (v -> v^2) ersetzen können

Der Hauptunterschied zu den Aussagen mit _ Platzhalter besteht darin, dass hier Positionsargumente verwendet werden können und das & vor den Ausdrücken den Umfang der Platzhalter (für den Leser und der Compiler).

Beachten Sie, dass ich in meinen Beispielen & genommen habe, aber die Verwendung von ? , _ , $ oder etwas anderem würde nichts an dem ändern Fall.

Scala verwendet _ für das erste Argument, _ für das zweite Argument usw., was kurz ist, aber Sie haben schnell keine Situationen mehr, in denen Sie es anwenden können (nicht wiederholen oder umkehren können) die Reihenfolge der Argumente). Es gibt auch kein Präfix ( & im obigen Vorschlag), das Funktionen von Ausdrücken unterscheidet, und dies ist in der Praxis ein weiteres Problem, das seine Verwendung verhindert. Als Praktiker verpacken Sie die beabsichtigten Inline-Funktionen in zusätzliche Klammern und geschweifte Klammern, in der Hoffnung, dass sie erkannt werden.

Daher würde ich sagen, dass die höchste Priorität bei der Einführung einer solchen Syntax darin besteht, dass sie eindeutig ist.

Bei den Präfixen für Argumente hat $ in der Shell-Scripting-Welt Tradition. Es ist immer gut, vertraute Zeichen zu verwenden. Wenn die |> von Elixir stammen, könnte dies ein Argument sein, um auch & von Elixir zu übernehmen, mit der Idee, dass Benutzer bereits in diesem Modus denken. (Vorausgesetzt, es gibt viele ehemalige Elixir-Benutzer da draußen ...)

Eine Sache, die eine solche Syntax wahrscheinlich niemals erfassen kann, ist das Erstellen einer Funktion, die N Argumente akzeptiert, aber weniger als N verwendet. Die $1 , $2 , $3 im Body implizieren die Existenz von 3 Argumenten, aber wenn Sie dies an eine Position bringen möchten, an der es mit 4 Argumenten aufgerufen wird (das letzte, das ignoriert wird), gibt es keine natürliche Möglichkeit, es auszudrücken. (Abgesehen von der Vordefinition von Identitätsfunktionen für jedes N und dem Umschließen des Ausdrucks mit einem dieser.) Dies ist nicht relevant für den motivierenden Fall, ihn nach einem |> , der jedoch nur ein Argument enthält.

Ich habe den @ MikeInnes- Trick erweitert, Colon als wären Funktionen Arrays:

struct LazyCall{F} <: Function
    func::F
    args::Tuple
    kw::Dict
end

Base.getindex(f::Function,args...;kw...) = LazyCall{typeof(f)}(f,args,kw)

function (lf::LazyCall)(vals...; kwvals...)

    # keywords are free
    kw = merge(lf.kw, kwvals)

    # indices of free variables
    x_ = findall(x->isa(x,Colon),lf.args)
    # indices of fixed variables
    x! = setdiff(1:length(lf.args),x_)

    # the calling order is aligned with the empty spots
    xs = vcat(zip(x_,vals)...,zip(x!,lf.args[x!])...)
    args = map(x->x[2],sort(xs;by=x->x[1]))

    # unused vals go to the end
    callit = lf.func(args...,vals[length(x_)+1:end]...; kw...)

    return callit
end

[1,2,3,4,1,1,5]|> replace![ : , 1=>10, 3=>300, count=2]|> filter[>(50)]  # == [300]

log[2](2) == log[:,2](2) == log[2][2]() == log[2,2]()  # == true

Es ist viel langsamer als Lambdas oder Threading-Makros, aber ich finde es super cool: p

Um die hier Kommentierenden daran zu erinnern, werfen Sie einen Blick auf die relevante Diskussion unter https://github.com/JuliaLang/julia/pull/24990.

Ich möchte Sie außerdem dazu ermutigen, https://github.com/c42f/Underscores.jl auszuprobieren, das eine funktionsverkettungsfreundliche Implementierung der Syntax _ für Platzhalter bietet. @jpivarski basierend auf Ihren Beispielen finden Sie es vielleicht ziemlich vertraut und bequem.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

ararslan picture ararslan  ·  3Kommentare

Keno picture Keno  ·  3Kommentare

StefanKarpinski picture StefanKarpinski  ·  3Kommentare

arshpreetsingh picture arshpreetsingh  ·  3Kommentare

i-apellaniz picture i-apellaniz  ·  3Kommentare