Rückkehr als schädlich? Kann Code auch ohne funktionieren?

45

OK, der Titel ist ein bisschen knifflig, aber im Ernst, ich war auf einem Tell, frag eine Weile nicht nach Kick. Mir gefällt, wie es Methoden anregt, wirklich objektorientiert als Botschaften verwendet zu werden . Aber das hat ein quälendes Problem, das mir durch den Kopf ging.

Ich habe den Verdacht, dass gut geschriebener Code gleichzeitig OO-Prinzipien und Funktionsprinzipien folgen kann. Ich versuche, diese Ideen in Einklang zu bringen, und der große Knackpunkt, auf den ich gestoßen bin, ist return.

Eine reine Funktion hat zwei Eigenschaften:

  1. Ein wiederholter Aufruf mit denselben Eingaben führt immer zum selben Ergebnis. Dies impliziert, dass es unveränderlich ist. Sein Zustand wird nur einmal gesetzt.

  2. Es entstehen keine Nebenwirkungen. Die einzige durch den Aufruf verursachte Änderung ist das Erzeugen des Ergebnisses.

Wie kann man also rein funktional sein, wenn man darauf verzichtet, returnErgebnisse zu kommunizieren?

Die Tell-Don't-Ask- Idee funktioniert unter Verwendung dessen, was einige als Nebenwirkung betrachten würden. Wenn ich mich mit einem Objekt befasse, frage ich es nicht nach seinem internen Zustand. Ich sage ihm, was ich tun muss, und er verwendet seinen internen Zustand, um herauszufinden, was mit dem zu tun ist, was ich ihm gesagt habe. Sobald ich es erzähle, frage ich nicht, was es getan hat. Ich erwarte nur, dass es etwas getan hat, was ihm befohlen wurde.

Ich denke an Tell, Ask nicht mehr als nur einen anderen Namen für die Verkapselung. Wenn ich benutze, habe returnich keine Ahnung, wie ich genannt wurde. Ich kann nicht sagen, dass es sich um ein Protokoll handelt, ich muss es erzwingen, um mit meinem Protokoll fertig zu werden. Was in vielen Fällen als interner Zustand ausgedrückt wird. Auch wenn das, was belichtet wird, nicht genau der Status ist, handelt es sich in der Regel nur um eine Berechnung, die für Status- und Eingabeargumente ausgeführt wird. Eine Schnittstelle zu haben, über die man reagieren kann, bietet die Möglichkeit, die Ergebnisse in einen aussagekräftigeren Zustand als interne Zustände oder Berechnungen zu überführen. Das ist die Weitergabe von Nachrichten . Siehe dieses Beispiel .

Vor langer Zeit, als Festplatten tatsächlich Festplatten enthielten und ein USB-Stick das war, was Sie im Auto taten, als das Rad zu kalt war, um es mit den Fingern zu berühren, wurde mir beigebracht, wie nervig Menschen Funktionen betrachten, die über Parameter verfügen. void swap(int *first, int *second)schien so praktisch, aber wir wurden ermutigt, Funktionen zu schreiben, die die Ergebnisse zurückgaben. Also nahm ich mir den Glauben zu Herzen und fing an, ihm zu folgen.

Aber jetzt sehe ich Leute, die Architekturen bauen, in denen Objekte ihre Konstruktionsweise kontrollieren lassen, wohin sie ihre Ergebnisse senden. Hier ist eine Beispielimplementierung . Das Injizieren des Output-Port-Objekts scheint wieder ein bisschen wie die Out-Parameter-Idee zu sein. Aber so sagen Sie anderen Objekten, was sie getan haben.

Als ich zum ersten Mal etwas über Nebenwirkungen erfuhr, stellte ich mir das als Ausgabeparameter vor. Wir sollten die Leute nicht überraschen, indem wir einen Teil der Arbeit auf überraschende Weise erledigen, dh indem wir uns nicht an die return resultKonvention halten. Nun sicher, ich weiß, dass es eine Menge paralleler asynchroner Threading-Probleme gibt, mit denen sich die Nebenwirkungen herumschlagen, aber Return ist eigentlich nur eine Konvention, bei der Sie das Ergebnis auf dem Stapel belassen, damit Sie es später entfernen können. Das ist alles was es wirklich ist.

Was ich wirklich zu fragen versuche:

Ist returndie einzige Möglichkeit, all diese Nebenwirkungen zu vermeiden und die Sicherheit des Fadens ohne Sperren usw. zu gewährleisten ? Oder kann ich sagen, nicht auf rein funktionale Weise fragen ?

kandierte_orange
quelle
3
Würden Sie Ihr Problem als gelöst betrachten, wenn Sie die Trennung von Befehlsabfragen nicht ignorieren?
rwong
30
Bedenken Sie, dass es ein Indiz dafür sein kann, dass Sie sich auf dogmagesteuertes Design einlassen, anstatt die Vor- und Nachteile der jeweiligen Situation zu ergründen.
Blrfl
3
Wenn die Leute davon sprechen, dass "return" schädlich ist, sagen sie im Allgemeinen, dass dies gegen eine strukturierte, nicht funktionierende Programmierung und eine einzelne return-Anweisung am Ende der Routine (und möglicherweise am Ende beider Seiten eines if / else-Blocks) ist ist selbst das letzte Element) ist darin nicht enthalten.
Random832
16
@jameslarge: Falsche Dichotomie. Es ist nicht dasselbe wie im Wilden Westen, dass Sie es sich nicht erlauben, durch dogmatisches Denken zu Entwürfen getrieben zu werden. Es geht darum, dem Dogma nicht zu erlauben, einem guten, einfachen und offensichtlichen Code im Wege zu stehen. Das universelle Gesetz ist für Lakaien; Kontext ist für Könige.
Nicol Bolas
4
In Ihrer Frage ist mir eines unklar: Sie sagen, Sie haben mit 'return' abgeschworen, aber soweit ich Ihnen sagen kann, beziehen Sie sich das nicht explizit auf Ihre anderen Konzepte oder sagen Sie, warum Sie dies getan haben. In Kombination mit der Definition einer reinen Funktion, einschließlich der Erzeugung eines Ergebnisses, erstellen Sie etwas, das sich nicht auflösen lässt.
Danikov

Antworten:

96

Wenn eine Funktion keine Nebenwirkungen hat und nichts zurückgibt, ist sie unbrauchbar. So einfach ist das.

Aber ich denke, Sie können einige Cheats verwenden, wenn Sie den Buchstaben der Regeln befolgen und die zugrunde liegende Argumentation ignorieren möchten . Wenn Sie beispielsweise einen out-Parameter verwenden, wird streng genommen kein return verwendet. Aber es tut genau das Gleiche wie eine Rückkehr, nur auf eine verschlungenere Art und Weise. Wenn Sie also glauben, dass die Rückgabe aus einem bestimmten Grund schlecht ist , ist die Verwendung eines out-Parameters aus denselben Gründen eindeutig schlecht.

Sie können mehr verschlungene Cheats verwenden. ZB ist Haskell berühmt für den IO-Monadentrick, bei dem Sie in der Praxis Nebenwirkungen haben können, aber theoretisch noch nicht unbedingt Nebenwirkungen haben. Continuation-Passing-Stil ist ein weiterer Trick, mit dem Sie Renditen vermeiden können, wenn Sie Ihren Code in Spaghetti verwandeln.

Das Fazit ist, dass ohne alberne Tricks die beiden Prinzipien der nebenwirkungsfreien Funktionen und "keine Rendite" einfach nicht kompatibel sind. Außerdem möchte ich darauf hinweisen, dass es sich bei beiden um wirklich schlechte Prinzipien (Dogmen) handelt, aber das ist eine andere Diskussion.

Regeln wie "Tell, Don't Ask" oder "No Side Effects" können nicht universell angewendet werden. Sie müssen immer den Kontext berücksichtigen. Ein Programm ohne Nebenwirkungen ist buchstäblich nutzlos. Auch reine Funktionssprachen erkennen das an. Sie bemühen sich vielmehr, die reinen Teile des Codes von denjenigen mit Nebenwirkungen zu trennen . Der Punkt der State- oder IO-Monaden in Haskell ist nicht, dass Sie Nebenwirkungen vermeiden - weil Sie dies nicht können -, sondern dass das Vorhandensein von Nebenwirkungen explizit durch die Funktionssignatur angezeigt wird.

Die Tell-Dont-Ask-Regel gilt für eine andere Art von Architektur - den Stil, bei dem Objekte im Programm unabhängige "Akteure" sind, die miteinander kommunizieren. Jeder Akteur ist grundsätzlich autonom und gekapselt. Sie können ihm eine Nachricht senden und er entscheidet, wie er darauf reagieren soll, aber Sie können den internen Zustand des Akteurs nicht von außen untersuchen. Dies bedeutet, dass Sie nicht feststellen können, ob eine Nachricht den internen Status des Akteurs / Objekts ändert. Zustand und Nebenwirkungen werden durch das Design ausgeblendet .

JacquesB
quelle
20
@CandiedOrange: Ob eine Methode direkt oder indirekt Nebenwirkungen hat, obwohl viele Aufrufebenen vorhanden sind, ändert konzeptionell nichts. Es ist immer noch ein Nebeneffekt. Wenn es jedoch darum geht, dass Nebenwirkungen nur durch injizierte Objekte auftreten und Sie steuern, welche Arten von Nebenwirkungen möglich sind, dann klingt dies nach einem guten Design. Es ist einfach nicht nebenwirkungsfrei. Es ist gut, aber es ist nicht rein funktional.
JacquesB
12
@LightnessRacesinOrbit: grep im stillen Modus hat einen Prozessrückgabecode, der verwendet wird. Wenn es das nicht hätte, wäre es wirklich nutzlos
unperson325680
10
@progo: Ein Rückkehrcode ist kein Nebeneffekt. es ist die Wirkung. Alles , was wir nennen eine Nebenwirkung eine Nebenwirkung genannt wird , weil es nicht der Rückkehrcode;)
Leichtigkeit Rennen mit Monica
8
@ Cubic das ist der Punkt. Ein Programm ohne Nebenwirkungen ist nutzlos.
Jens Schauder
5
@ Kathreadler Das ist ein Nebeneffekt :)
Andres F.
32

Tell, Don't Ask beinhaltet einige grundlegende Annahmen:

  1. Sie verwenden Objekte.
  2. Ihre Objekte haben Zustand.
  3. Der Zustand Ihrer Objekte beeinflusst deren Verhalten.

Nichts davon gilt für reine Funktionen.

Sehen wir uns also an, warum wir die Regel "Tell, Don't Ask" haben. Diese Regel ist eine Warnung und eine Erinnerung. Es kann wie folgt zusammengefasst werden:

Erlaube deiner Klasse, ihren eigenen Zustand zu verwalten. Fragen Sie ihn nicht nach seinem Status und ergreifen Sie dann Maßnahmen, die auf diesem Status basieren. Sagen Sie der Klasse, was Sie wollen, und lassen Sie sie auf der Grundlage ihres eigenen Zustands entscheiden, was zu tun ist.

Anders ausgedrückt, die Klassen sind allein dafür verantwortlich, ihren eigenen Zustand aufrechtzuerhalten und danach zu handeln. Darum geht es bei der Kapselung.

Von Fowler :

Tell-Don't-Ask ist ein Prinzip, mit dem man sich daran erinnert, dass es bei der Objektorientierung darum geht, Daten mit den Funktionen zu bündeln, die mit diesen Daten arbeiten. Es erinnert uns daran, dass wir, anstatt ein Objekt nach Daten zu fragen und auf diese Daten zu reagieren, einem Objekt sagen sollten, was zu tun ist. Dies ermutigt uns, das Verhalten in ein Objekt zu verschieben, das mit den Daten übereinstimmt.

Um es noch einmal zu wiederholen: Nichts davon hat etwas mit reinen oder sogar unreinen Funktionen zu tun, es sei denn, Sie setzen den Zustand einer Klasse der Außenwelt aus. Beispiele:

TDA-Verstoß

var color = trafficLight.Color;
var elapsed = trafficLight.Elapsed;
If (color == Color.Red && elapsed > 2.Minutes)
    trafficLight.ChangeColor(green);

Keine TDA-Verletzung

var result = trafficLight.ChangeColor(Color.Green);

oder

var result = await trafficLight.ChangeColorWhenReady(Color.Green);     

In beiden letzteren Beispielen behält die Ampel die Kontrolle über ihren Zustand und ihre Aktionen.

Robert Harvey
quelle
Warten Sie eine Sekunde, Verschlüsse können rein sein. Sie haben Zustand (sie nennen es lexikalischen Geltungsbereich). und dieser lexikalische Bereich kann ihr Verhalten beeinflussen. Sind Sie sicher, dass TDA nur relevant ist, wenn Objekte betroffen sind?
candied_orange
7
@CandiedOrange-Verschlüsse sind nur dann rein, wenn Sie die geschlossenen Bindungen nicht ändern. Andernfalls verlieren Sie die referenzielle Transparenz, wenn Sie die vom Closure zurückgegebene Funktion aufrufen.
Jared Smith
1
@JaredSmith und ist das nicht dasselbe, wenn Sie über Objekte sprechen? Ist das nicht einfach das Problem der Unveränderlichkeit?
candied_orange
1
@bdsl - Und jetzt haben Sie versehentlich genau darauf hingewiesen, wo jede Diskussion dieser Art mit Ihrem trafficLight.refreshDisplay-Beispiel hingeht. Wenn Sie sich an die Regeln halten, erhalten Sie ein hochflexibles System, das nur der ursprüngliche Codierer versteht. Ich wette sogar, dass nach ein paar Jahren Pause selbst der ursprüngliche Programmierer wahrscheinlich nicht versteht, was sie auch getan haben.
Dunk
1
"Albern", wie in "Öffne nicht die anderen Objekte und schaue nicht auf ihre Eingeweide, sondern spucke stattdessen deine eigenen Eingeweide (falls du welche hast) in die Methoden der anderen Objekte, das ist der Weg" -silly.
Joker_vD
30

Wenn ich mich mit einem Objekt befasse, frage ich es nicht nach seinem internen Zustand. Ich sage ihm, was ich tun muss, und er verwendet seinen internen Zustand, um herauszufinden, was mit dem zu tun ist, was ich ihm gesagt habe.

Sie fragen nicht nur nach dem internen Status , sondern auch, ob er überhaupt einen internen Status hat .

Auch sagen, fragen Sie nicht! bedeutet nicht , kein Ergebnis in Form eines Rückgabewerts zu erhalten (bereitgestellt durch eine returnAnweisung innerhalb der Methode). Es bedeutet nur, dass es mir egal ist, wie Sie es machen, aber machen Sie diese Verarbeitung! . Und manchmal will man sofort das Bearbeitungsergebnis ...

Timothy Truckle
quelle
1
CQS würde jedoch bedeuten, dass das Ändern des Status und das Erhalten von Ergebnissen getrennt werden sollten
jk.
7
@jk. Wie üblich: In der Regel sollten Sie trennen Zustandsänderung und ein Ergebnis der Rückkehr aber in seltenen Fällen gibt es gute Gründe , das zu kombinieren. Beispiel: Eine Iterator- next()Methode sollte nicht nur das aktuelle Objekt zurückgeben, sondern auch den internen Status des Iterators ändern, sodass der nächste Aufruf das nächste Objekt zurückgibt ...
Timothy Truckle
4
Genau. Ich denke, das Problem von OP ist einfach auf Missverständnisse zurückzuführen. Und das Beheben dieses Missverständnisses lässt das Problem verschwinden.
Konrad Rudolph
@KonradRudolph Plus, ich glaube nicht, dass das hier das einzige Missverständnis ist. Ihre Beschreibung einer "reinen Funktion" beinhaltet "Ihr Zustand wird nur einmal gesetzt." Ein anderer Kommentar deutet darauf hin, dass dies den Kontext eines Abschlusses bedeuten könnte, aber die Formulierung klingt für mich seltsam.
Izkata
17

Wenn Sie returnals "schädlich" betrachten (um in Ihrem Bild zu bleiben), dann machen Sie stattdessen eine Funktion wie

ResultType f(InputType inputValue)
{
     // ...
     return result;
}

Bauen Sie es in einer Message-Passing-Weise:

void f(InputType inputValue, Action<ResultType> g)
{
     // ...
     g(result);
}

Solange fund gnebenwirkungsfrei sind, sind Verkettungen auch nebenwirkungsfrei. Ich denke, dieser Stil ähnelt dem, was man auch als Continuation-Passing-Stil bezeichnet .

Ob dies wirklich zu "besseren" Programmen führt, ist umstritten, da es einige Konventionen verletzt. Der deutsche Softwareentwickler Ralf Westphal hat ein ganzes Programmiermodell erstellt, er nannte es "Event Based Components" mit einer Modellierungstechnik, die er "Flow Design" nennt.

Beginnen Sie im Abschnitt "In Ereignisse übersetzen" dieses Blogeintrags , um einige Beispiele anzuzeigen . Für den vollständigen Ansatz empfehle ich sein E-Book "Messaging als Programmiermodell - OOP so tun, als ob Sie es gemeint hätten" .

Doc Brown
quelle
24
If this really leads to "better" programs is debatableWir müssen uns nur den Code ansehen, der im ersten Jahrzehnt dieses Jahrhunderts in JavaScript geschrieben wurde. JQuery und seine Plugins waren anfällig für dieses Paradigma Rückrufe ... Rückrufe überall . Zu viele verschachtelte Rückrufe machten das Debuggen zu einem Albtraum. Code muss immer noch von Menschen gelesen werden, ungeachtet der Exzentrizität des Software-Engineerings und seiner "Prinzipien"
Laiv,
1
Selbst dann müssen Sie irgendwann eine Aktion ausführen, die einen Nebeneffekt hervorruft, oder Sie müssen einen Weg finden, um aus dem CPS-Teil zurückzukehren
jk.
12
@Laiv CPS wurde als Optimierungstechnologie für Compiler erfunden. Eigentlich hatte niemand damit gerechnet, dass Programmierer den Code auf diese Weise von Hand schreiben würden.
Joker_vD
3
@CandiedOrange In Sloganform sagt "return nur der Fortsetzung, was zu tun ist". Tatsächlich war die Erstellung des Programms durch den Versuch motiviert , Hewitts Schauspieler-Modell zu verstehen, und kam zu dem Schluss, dass Schauspieler und Abschlüsse dasselbe waren.
Derek Elkins
2
Sicherlich können Sie Ihre gesamte Anwendung als eine Reihe von Funktionsaufrufen schreiben, die nichts zurückgeben. Wenn Sie nichts dagegen haben, dass es eng gekoppelt ist, brauchen Sie nicht einmal die Ports. Aber die meisten vernünftigen Anwendungen verwenden Funktionen, die Dinge zurückgeben, weil ... sie vernünftig sind. Und wie ich glaube, dass ich in meiner Antwort ausreichend demonstriert habe, können Sie returnDaten von Funktionen verwenden und sich trotzdem an Tell Don't Ask halten, solange Sie nicht den Status eines Objekts befehlen.
Robert Harvey
7

Die Weitergabe von Nachrichten ist von Natur aus effektiv. Wenn Sie sagen , ein Objekt zu tun , etwas, erwarten Sie es eine Wirkung auf etwas haben. Wenn der Nachrichtenhandler rein wäre, müssten Sie ihm keine Nachricht senden.

In Systemen mit verteilten Akteuren wird das Ergebnis einer Operation normalerweise als Nachricht an den Absender der ursprünglichen Anforderung zurückgesendet. Der Absender der Nachricht wird entweder implizit von der Actor-Laufzeit zur Verfügung gestellt oder (gemäß Konvention) explizit als Teil der Nachricht übergeben. Bei der synchronen Nachrichtenübergabe entspricht eine einzelne Antwort einer returnAnweisung. Bei der asynchronen Nachrichtenübergabe ist die Verwendung von Antwortnachrichten besonders nützlich, da sie die gleichzeitige Verarbeitung in mehreren Akteuren ermöglicht und dennoch Ergebnisse liefert.

Die Übergabe des "Absenders", an den das Ergebnis explizit übermittelt werden soll, modelliert im Grunde den Fortsetzungs-Übergabestil oder die gefürchteten Parameter - mit der Ausnahme, dass Nachrichten an sie übergeben werden, anstatt sie direkt zu mutieren.

Bergi
quelle
5

Diese ganze Frage erscheint mir als "Level-Verletzung".

Sie haben (mindestens) folgende Stufen in einem Großprojekt:

  • Die Systemebene zB E-Commerce-Plattform
  • Die Subsystemebene, z. B. Benutzervalidierung: Server, AD, Front-End
  • Die einzelne Programmebene zB eine der oben genannten Komponenten
  • Das Actor / Module Level [dies wird abhängig von der Sprache undeutlich]
  • Die Methoden- / Funktionsebene.

Und so weiter bis zu einzelnen Token.

Es ist nicht wirklich erforderlich, dass eine Entität auf Methoden- / Funktionsebene nicht zurückgibt (auch wenn sie nur zurückgibt this). Und es gibt (in Ihrer Beschreibung) keine Notwendigkeit, dass eine Entität auf der Ebene des Akteurs etwas zurückgibt (abhängig von der Sprache, die möglicherweise nicht einmal möglich ist). Ich denke, die Verwirrung besteht darin, diese beiden Ebenen miteinander zu verschmelzen, und ich würde argumentieren, dass sie eindeutig begründet werden sollten (selbst wenn ein bestimmtes Objekt tatsächlich mehrere Ebenen umfasst).

Jared Smith
quelle
2

Sie erwähnen, dass Sie sowohl dem OOP-Prinzip "Tell, Don't Ask" als auch dem Funktionsprinzip reiner Funktionen entsprechen möchten, aber ich verstehe nicht ganz, wie Sie dies veranlasst haben, die return-Anweisung zu umgehen.

Eine relativ gebräuchliche Alternative, um diesen beiden Prinzipien zu folgen, besteht darin, die return-Anweisungen zu all-in und unveränderliche Objekte nur mit Getern zu verwenden. Der Ansatz ist dann, dass einige der Getter ein ähnliches Objekt mit einem neuen Status zurückgeben, anstatt den Status des ursprünglichen Objekts zu ändern.

Ein Beispiel für diesen Ansatz sind die integrierten Python- tupleund frozensetDatentypen. Hier ist eine typische Verwendung eines Frozensets:

small_digits = frozenset([0, 1, 2, 3, 4])
big_digits = frozenset([5, 6, 7, 8, 9])
all_digits = small_digits.union(big_digits)

print("small:", small_digits)
print("big:", big_digits)
print("all:", all_digits)

Mit dem folgenden Ausdruck wird demonstriert, dass die Vereinigungsmethode ein neues Frozenset mit eigenem Status erstellt, ohne die alten Objekte zu beeinflussen:

klein: Frosenset ({0, 1, 2, 3, 4})

big: frozenset ({5, 6, 7, 8, 9})

all: frozenset ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})

Ein weiteres umfangreiches Beispiel für ähnliche unveränderliche Datenstrukturen ist die Bibliothek Immutable.js von Facebook . In beiden Fällen beginnen Sie mit diesen Bausteinen und können übergeordnete Domänenobjekte erstellen, die den gleichen Grundsätzen folgen. Auf diese Weise erhalten Sie einen funktionalen OOP-Ansatz, mit dem Sie die Daten und die Gründe dafür einfacher kapseln können. Durch die Unveränderlichkeit können Sie auch den Vorteil nutzen, solche Objekte zwischen Threads gemeinsam nutzen zu können, ohne sich um Sperren kümmern zu müssen.

yoniLavi
quelle
1

Ich habe den Verdacht, dass gut geschriebener Code gleichzeitig OO-Prinzipien und Funktionsprinzipien folgen kann. Ich versuche, diese Ideen in Einklang zu bringen, und der große Knackpunkt, auf den ich gestoßen bin, ist die Rückkehr.

Ich habe mein Bestes gegeben, um einige der Vorteile der imperativen und funktionalen Programmierung in Einklang zu bringen (natürlich nicht alle Vorteile, aber ich versuche, den Löwenanteil von beiden zu bekommen), obwohl dies returneigentlich von grundlegender Bedeutung ist in vielen fällen eine einfache art und weise für mich.

Um returnAussagen zu vermeiden , habe ich in der letzten Stunde versucht, darüber nachzudenken, und im Grunde ist mein Gehirn mehrmals überfüllt. Ich sehe die Anziehungskraft davon darin, die stärkste Ebene der Verkapselung und des Versteckens von Informationen zugunsten von sehr autonomen Objekten zu erzwingen, denen lediglich gesagt wird, was zu tun ist, und ich mag es, die äußersten Grenzen von Ideen zu erkunden, wenn ich nur versuche, eine Verbesserung zu erreichen Verständnis dafür, wie sie funktionieren.

Wenn wir das Ampel-Beispiel verwenden, dann würde ein naiver Versuch sofort ein solches Ampel-Wissen über die gesamte Welt, die es umgibt, vermitteln wollen, und das wäre sicherlich aus Kopplungsperspektive unerwünscht. Wenn ich das richtig verstehe, abstrahieren Sie dies und entkoppeln sich, um das Konzept der E / A-Ports zu verallgemeinern, die Nachrichten und Anforderungen, nicht Daten, durch die Pipeline weiterleiten, und fügen im Grunde genommen die gewünschten Interaktionen / Anforderungen untereinander in diese Objekte ein während sie sich nicht bewusst sind.

Die Knotenpipeline

Bildbeschreibung hier eingeben

Und dieses Diagramm ist ungefähr so ​​weit wie ich versucht habe, es zu skizzieren (und obwohl es einfach ist, musste ich es immer wieder ändern und neu überdenken). Ich neige dazu zu glauben, dass ein Design mit dieser Ebene der Entkopplung und Abstraktion es sehr schwierig finden würde, in Codeform darüber nachzudenken, weil es für die Orchestratoren, die all diese Dinge für eine komplexe Welt verkabeln, sehr schwierig sein könnte Behalten Sie alle diese Interaktionen und Anforderungen im Auge, um die gewünschte Pipeline zu erstellen. In visueller Form mag es jedoch recht einfach sein, diese Dinge einfach als Diagramm zu zeichnen und alles miteinander zu verknüpfen und zu sehen, wie Dinge interaktiv geschehen.

In Bezug auf die Nebenwirkungen konnte ich feststellen, dass dies in dem Sinne frei von "Nebenwirkungen" ist, dass diese Anforderungen auf dem Aufrufstapel zu einer Befehlskette für jeden auszuführenden Thread führen können, z. B. (Ich zähle dies nicht als "Nebeneffekt" im pragmatischen Sinne, da es keinen für die Außenwelt relevanten Zustand verändert, bis solche Befehle tatsächlich ausgeführt werden - das praktische Ziel für mich in den meisten Programmen ist es nicht, Nebeneffekte zu beseitigen, sondern sie zu verschieben und zu zentralisieren) . Und außerdem könnte die Befehlsausführung eine neue Welt ausgeben, anstatt die existierende zu mutieren. Mein Gehirn ist wirklich belastet, wenn es nur versucht, all dies zu verstehen, ohne einen Versuch zu unternehmen, diese Ideen zu prototypisieren. Ich habe auch nicht

Wie es funktioniert

Um das zu verdeutlichen, habe ich mir vorgestellt, wie Sie das tatsächlich programmieren. Die Art und Weise, wie ich es funktionierte, war das obige Diagramm, das den Workflow für Benutzer (Programmierer) aufzeichnete. Sie können eine Ampel in die Welt ziehen, eine Zeitschaltuhr ziehen und eine verstrichene Zeitspanne festlegen (wenn Sie sie "konstruieren"). Der Timer verfügt über ein On IntervalEreignis (Ausgangsport), das Sie an die Ampel anschließen können, damit die Ampel bei solchen Ereignissen durch ihre Farben wechselt.

Die Ampel kann dann beim Umschalten auf bestimmte Farben Ausgaben (Ereignisse) ausgeben, wie zum Beispiel, On Redan welchem ​​Punkt wir einen Fußgänger in unsere Welt ziehen und dieses Ereignis den Fußgänger anweisen, mit dem Gehen zu beginnen ... oder wir könnten Vögel hineinziehen Wenn das Licht rot wird, sagen wir den Vögeln, sie sollen fliegen und mit den Flügeln schlagen. Oder wenn das Licht rot wird, sagen wir einer Bombe, sie soll explodieren - was immer wir wollen und die Objekte sollen explodieren einander völlig vergessen und nichts anderes tun, als sich indirekt mitzuteilen, was durch dieses abstrakte Eingabe- / Ausgabekonzept zu tun ist.

Und sie kapseln ihren Zustand vollständig ein und verraten nichts darüber (es sei denn, diese "Ereignisse" werden als TMI betrachtet, an diesem Punkt müsste ich viel überdenken), sie sagen einander, dass sie indirekt etwas tun sollen, sie fragen nicht. Und sie sind überaus entkoppelt. Nichts weiß etwas anderes als diese verallgemeinerte Eingabe / Ausgabe-Port-Abstraktion.

Praktische Anwendungsfälle?

Ich könnte diese Art von Dingen als eine domänenspezifische eingebettete Sprache auf hoher Ebene in bestimmten Domänen sehen, um all diese autonomen Objekte zu orchestrieren, die nichts über die umgebende Welt wissen, nichts über ihren internen Zustand nach der Konstruktion preisgeben und im Grunde nur Anfragen verbreiten untereinander, die wir nach Herzenslust verändern und optimieren können. Im Moment denke ich, dass dies sehr domänenspezifisch ist, oder ich habe einfach nicht genug darüber nachgedacht, weil es für mich sehr schwierig ist, mein Gehirn mit den Dingen zu beschäftigen, mit denen ich mich regelmäßig entwickle (ich arbeite oft damit) eher Low-Mid-Level-Code), wenn ich Tell, Don't Ask zu solchen Extremitäten interpretieren und den stärksten Grad der Kapselung, den man sich vorstellen kann, wünschen würde. Aber wenn wir mit Abstraktionen auf hoher Ebene in einer bestimmten Domäne arbeiten,

Signale und Slots

Dieses Design kam mir merkwürdig bekannt vor, bis mir klar wurde, dass es sich im Grunde genommen um Signale und Slots handelt, wenn wir nicht die Nuancen berücksichtigen, wie es implementiert wird. Die Hauptfrage für mich ist, wie effektiv wir diese einzelnen Knoten (Objekte) im Graphen so programmieren können, dass sie sich strikt an Tell, Don't Ask halten, bis zum Grad der Vermeidung von returnAussagen, und ob wir den Graphen ohne Mutationen auswerten können (in parallel, zB fehlende Verriegelung). Hier liegt der magische Vorteil nicht darin, wie wir diese Dinge potenziell miteinander verbinden, sondern wie sie ohne Mutationen in diesem Grad der Verkapselung implementiert werden können. Beides scheint mir machbar zu sein, aber ich bin mir nicht sicher, wie weit verbreitet es sein würde, und da bin ich ein bisschen ratlos, wenn ich versuche, mögliche Anwendungsfälle durchzuarbeiten.

Drachen Energie
quelle
0

Ich sehe hier ein deutliches Sicherheitsleck. Es scheint, dass "Nebeneffekt" ein bekannter und allgemein verständlicher Begriff ist, aber in Wirklichkeit ist dies nicht der Fall. Abhängig von Ihren Definitionen (die im OP tatsächlich fehlen) können Nebenwirkungen (wie von @JacquesB erklärt) völlig notwendig oder gnadenlos inakzeptabel sein. Oder, um einen Schritt in Richtung Klärung zu machen, muss man zwischen erwünschten Nebenwirkungen, die man nicht gerne versteckt (an diesen Stellen taucht das berühmte Haskell-IO auf: Es ist nichts anderes als eine Möglichkeit, explizit zu sein) und unerwünschten Nebenwirkungen als unterscheiden Ergebnis von Code-Fehlern und solchen Dingen . Das sind ziemlich unterschiedliche Probleme und erfordern daher unterschiedliche Überlegungen.

Also schlage ich vor, mit dem Umformulieren zu beginnen: "Wie definieren wir Nebenwirkungen und was sagen bestimmte Definitionen über die Wechselbeziehung mit der" return "-Anweisung aus?"

Sereja Bogolubov
quelle
1
"An diesen Punkten taucht das berühmte Haskell-E / A auf: Es ist nichts anderes als eine Möglichkeit, explizit zu sein" - Explizite sind sicherlich ein Vorteil von Haskells monadischem E / A, aber es gibt noch einen weiteren Punkt: Es bietet die Möglichkeit, den Nebeneffekt vollständig zu isolieren außerhalb der Sprache - obwohl gemeinsame Implementierungen nicht tatsächlich das tun, vor allem aufgrund Effizienz betrifft, vom Konzept her ist es wahr: die IO Monade eine Möglichkeit der Rückkehr eine Anweisung zu einem gewissen Umgebung betrachtet werden können , die vollständig außerhalb der ist Haskell-Programm zusammen mit einem Verweis auf eine Funktion, die nach Abschluss fortgesetzt werden soll.
Jules