Frage von KnorxyThieus, 68

Gibt es eigentlich in der "klassischen" Programmierung eine Art Operator, die den vorhergehenden Ausdruck ignoriert?

Hallo,

ich programmiere jetzt schon seit einigen Jahren, nach Basic-Einstieg nun seit einem Jahr mit C#, doch schon seit Längerem geht mir diese Frage durch den Kopf:

Gibt es eigentlich in der "klassischen", d. h. nicht funktional konzipierten Programmierung (mit der ich bisher nur spurenweise durch LINQ in Berührung gekommen bin) eine Art von Operator, die den ersten Operand ignoriert?

Beispiel:

return a++ ~ a^2; /* Die Tilde (~) sei jetzt mal dieser "magische" Operator,
                     die Anweisung würde erst a inkrementieren und dann
                     das Quadrat vom inkrementierten a zurückgeben.

x =                       // Dies würde im Falle von y <= 10 false zurückgeben,
    (y > 10)              // andernfalls die void-Methode Log aufrufen
    ? Log("Aha!") ~ true  // und dann true zurückgeben.
    : false;

Die Liste möglicher Anwendungen ist lang ... ist das ein misskonzipiertes Hirngespinst, gibt es das vielleicht sogar schon oder wäre das - richtig ausgedrückt - tatsächlich schon ein Bestandteil funktionalen Codes?
Eine funktionale Sprache komplett zu erlernen, war mir leider bislang aus zeitlichen Gründen nicht möglich, außerdem würde ich damit unserem Informatikunterricht des nächsten Semesters zum Thema Haskell vorweggreifen :/

Würde mich sehr über eure Antworten freuen! :-)

Mit freundlichen Grüßen,
KnorxThieus

Antwort
von PWolff, 27

In Mathematica (zwar auch keine prozedurale Sprache, aber das spielt in diesem Fall ausnahmsweise keine Rolle) kann man einfach

(ausdruck1; ausdruck2; ausdruck3; ...; ausdruck_n)

verwenden; die Ausdrücke werden der Reihe nach ausgewertet und das Ergebnis des Gesamtausdrucks ist das Ergebnis des letzten Ausdrucks.

In C# kann man das - wenn auch etwas sufwendiger - simulieren:

return (new object[] { a++, Math.Pow(a, 2) }).Last();

Ggf. mit dem nötigen Typecast

Übrigens steht der Operator "^" in C# und Sprachen vergleichbarer Syntax nicht für die Potenz, sondern für den bitweisen XOR-Operator.

Kommentar von KnorxyThieus ,

Wow, ist das diese "Wolfram Language"? Die wollte ich mir auch schon mal anschauen ... guter Hinweis!

In C# aber mit der Einschränkungen, dass keine void-Aufrufe möglich sind ... :(

Wenn man eine Funktion wie

public static Do<T>(this T obj, Action action)
{
    action();
    return obj;
}
// Pseudocode

hätte, angewendet etwa:

x =
(y > 10)
? true.Do(() => Log("Aha!"))
: false;

, käme man ja auch auf dasselbe.
Bloß ist beides syntaktisch immer doch recht umständlich ... :(

Übrigens steht der Operator "^" in C# und Sprachen vergleichbarer Syntax nicht für die Potenz, sondern für den bitweisen XOR-Operator.

Ups, glatt vergessen ^^ ;)

Antwort
von TUrabbIT, 31

Ich meine in C gibt es einen Operator der das hintereinander ausführen von zwei Anweisungen ermöglicht. Weiß es aber nicht mehr sicher.
Dazu gibt es bedingte Ausdrücke in Kurzschrift die in einer Zeile die Auswertung machen und je nachdem die eine oder andere Anweisung ausführen.

Antwort
von Dory1, 16

In Javascript gibt es übrigens tatsächlich so etwas, den Komma-Operator:

(function() {
return console.log("hallo"), "welt";
})();

Ausgabe:

> hallo
> "welt"

:)

Kommentar von KnorxyThieus ,

Oh, das klingt ja interessant!

Wie funktioniert der genau? Wieso wird "Welt" mit Anführungsstrichen ausgegeben? ☺

Kommentar von Dory1 ,

Der Operator funktioniert tatsächlich so, dass der erste Operand nur ausgewertet wird, der zweite Operand ist dann aber der tatsächliche Wert der Zuweisung: 

var x = (1, 2);      // x ist jetzt 2

Auf dein Beispiel bezogen wäre also so etwas möglich:

return (a++, Math.pow(a, 2));

In meinem Beispiel ist "Hallo" eine Konsolenausgabe (daher ohne Anführungsstriche), "Welt" ist der von der Funktion tatsächlich zurückgegebene String (keine Konsolenausgabe) - die Darstellung ist aber vermutlich auch je nach Browser anders.

Antwort
von Dory1, 37

Inwiefern ignoriert dein fiktiver Operator denn etwas? Sieht für mich eher so aus als würdest du mehrere komplexe Anweisungen in ein return statement quetschen wollen. Was spricht denn gegen die Verwendung einer Funktion?


Anstatt:

return a++ ~ a^2;


(Pseudocode):

function plusEinsUndHochZwei(a) {
  a++; 
  return a^2;
}

...

return plusEinsUndHochZwei(a);

Zweites Beispiel:

function logAndReturnTrue() {
   Log("Aha!");
   return true;
}

x = (y < 10) ? logAndReturnTrue() : false;


Ist nebenbei noch viel einfacher zu lesen.

Kommentar von Dory1 ,

Du bist übrigens nicht gezwungen den ?: Operator zu verwenden ;)

if (y < 10) {
  Log("Aha");
  return true;
} 
return false;

tut's doch auch :)

?: ist nur in den Fällen schöner wenn eben keine komplexen Operationen mehr durchgeführt werden müssen:

return y < 10 ? 0 : 1;
Kommentar von KnorxyThieus ,

Na ja, mich begeistert das Konzept langer Ausdrücke immer, es kann doch nicht sein, dass man wegen einer einzelnen Bedingung davon abweichen muss!

Kommentar von Dory1 ,

Viele Programmierer sind davon leider begeistert ;) Die Frage ist ob es dich noch begeistert wenn du deinen eigenen Code mal nach 2-3 Jahren anpassen musst :) Schreib lieber lesbaren Code anstatt krampfhaft Zeilen zu sparen. Du wirst es dir selbst danken.

Kommentar von KnorxyThieus ,

Wenn man den Code versteht, ist er effizienter (vom Schreiben her) ... das ist wie mit meiner Handschrift. Die ist auch grauenvoll, aber ich kann sie lesen.
Mit dem Unterschied, dass solche Syntaxen hier genormt sind. ♥

Kommentar von regex9 ,

In der Programmierung ist eine einfache Lesbarkeit eines der wichtigsten Ziele. Diese Anforderung könnte in so einem Fall nicht mehr erfüllt werden. Vor allem, wenn dann irgendwelche Programmierer noch versuchen würden, solche Ausdrücke zu verschachteln.

Die Implementierung eines solchen Features würde einen schlechten Programmierstil nur unterstützen und das kann kein ernsthaftes Ziel sein.

Kommentar von KnorxyThieus ,

Wann ist ein Stil denn gut? Ich kenne mich wie gesagt mit FP nicht sonderlich aus, glaube aber, auch auf StackOverflow schon hochbewertete Extensions in diesem Stil gelesen zu haben.

Kommentar von regex9 ,

Folgendes zeichnet (meiner Meinung nach) guten Stil aus:

  • eindeutige und aussagekräftige Namen
  • Einhaltung der Rechtschreibung und Grammatik
  • Einhaltung strikter Regeln (bspw. bei JS immer ein Semikolon am Ende eines Befehls, auch wenn es nicht in jedem Fall zwingend notwendig ist)
  • Stringenz
  • logische Anordnung und Strukturierung von Elementen (z.B. sichtbare Elemente vor nicht sichtbaren Elementen, etc.)
  • logische Aufteilung von Code (eine Funktion berechneGeschwindigkeit bspw. konzentriert sich nur auf die eine Aufgabe, die Geschwindigkeit zu berechnen. Andere Aufgaben (Ausgabe, etc.) werden woanders übernommen)
  • Übersichtlichkeit (durch Einrückungen, Leerzeichen, Grouping, Zeilenumbrüche bei zu langen Zeilen)
  • klare Deklarationen, wie z.B. access modifier vor jedem Klassen-/Objektzustand (Methoden, Felder, Delegates, etc.) oder === statt == bei Vergleichen mit JavaScript
  • Entfernen unnötiger Codeblöcke und überflüssiger Zeilen (z.B. eine Leerzeile zwischen 2 schließenden geschweiften Klammern)
  • lange Ausdrücke zwischenspeichern
  • Ausdrücke wie den ternären Operator nur dann verwenden, wenn der Ausdruck kurz bleibt und nicht verschachtelt wird (optimalerweise nimmt dieser Operator 3 Zeilen ein)
  • lesbar und leicht nachvollziehbar programmieren (z.B. a == null statt null == a)
  • Kommentare da angeben, wo zumindest notwendig
  • usw.
Kommentar von KnorxyThieus ,

Hm, grade das mit der Lesbarkeit meinte ich ja: Ich finde eigentlich, dass man a == null ebenso gut wie null == a versteht, welcher Programmierer ist denn dazu nicht in der Lage? :(

Was die langen Ausdrücke betrifft: Wenn man diese im Falle einer kleinen Änderung komplett redesignen muss, liegt doch da ein systematischer Fehler des Syntaxgebrauchs vor, finde ich. Hat man etwa:

return (a % 2) == 0
  ? a
  : b;

und möchte nun auch b einschränken, so führt das zu:

return (a % 2) == 0
? a
: ((b % 2) == 0 ? b : default(int));

Wenn man jetzt alles in ein if-Konstrukt übertragen sollte, so müsste doch etwas nicht stimmen mit dem Konzept dieser Syntax ...

Kommentar von Dory1 ,

Alles eine Frage der Formatierung:

return a % 2 == 0
? a
: b % 2 == 0 ? b : default(int);

Finde ich schon lesbarer



Kommentar von KnorxyThieus ,

OK, war auch nur ein Beispiel.
Der Punkt ist, bei der Erweiterung des Algorithmus kann/muss einfach irgendwann der Zeitpunkt kommen, an dem er als einzelner Ausdruck für manche Leute zu komplex erscheint ...

Führt jetzt aber etwas vom "Zauberoperator" weg.

Kommentar von regex9 ,

(...) ich finde eigentlich, dass man a == null ebenso gut wie null == a versteht, (...)



Es geht um einen einheitlich schnellen Lesefluss. Natürlich sollte dieser Ausdruck von jedem Programmierer verstanden werden, doch hemmt er in dem Moment des Lesens, da man entgegen dem üblichen Gebrauch ein wenig umdenken muss.

Für das Beispiel des erweiterten ternären Operators gibt es 2 Lösungsmöglichkeiten: Entweder man speichert das Ergebnis der verschachtelten Operation in einer Variable oder man lagert die Operation in eine extra Funktion aus. Eine entsprechend eingerückte Formatierung wie Dory1 sie vorschlägt würde ich nur in so einem simplen Fall vorsehen, wo die Verschachtelung nicht über 2 Ebenen hinausläuft.

Suboptimal ist diese Verschachtelung auf jeden Fall, denn der Leser muss hier einen komplexeren Ausdruck erfassen. Geht man hingegen Schritt für Schritt vor, so wie es die oben erwähnten Lösungsmöglichkeiten tun, entlastet dies den Denkprozess.

Ein weiterer Punkt der gegen solche Kurzschreibweisen spricht ist die verminderte Möglichkeit, diese Code-Abschnitte zu testen oder schnell zu erweitern. In einem if-Block lässt sich z.B. fix eine zusätzliche Log-Ausgabe o.ä. ergänzen.

Kommentar von KnorxyThieus ,

Tja, in dieser Hinsicht bin ich dann doch nicht gänzlich vom Konzept überzeugt, in dem es mehrere Ausdrucksmöglichkeiten gibt ... wird Zeit, dass jemand eine noch bessere Sprache entwickelt, die z.B. nur auf Ausdrücken basiert (die sich dann auch entsprechend debuggen oder erweitern ließen). Oder gibt es so eine bereits?

Kommentar von regex9 ,

Sicherlich gibt es bereits eine esoterische Sprache, die an so etwas heranreicht.

Viel Spaß beim Bau von Compiler und Debugger.

Kommentar von KnorxyThieus ,

ch weiß nicht, ob ich mich richtig ausgedrückt habe ... in Pseudocode etwa:

class Bruch
{
 int Zaehler, Nenner;
 int Kuerzen =
  Enumerable.Range(2, Max(Zaehler, Nenner) - 1)
  .Reverse()
  .For(i =>
   (Zaehler, Nenner) % i == 0
   ? (Zaehler, Nenner) /= i)
  .Create<Bruch>
}

Also eine Sprache, in der Funktionen und Ausdrücke quasi gleichberechtigt sind, damit man nicht, wie in z. B. C#, zwischen prozeduralem Code und Lambdaausdrücken wechseln muss und Konstrukte wie der folgende entfallen:

private IEnumerable<T> Test(IEnumerable<T> x)
{
   return x
      .Select(y => y.ShadowClone())
      .Where(y != null)
      .Reverse();
}

Wo die ganze Ausdruckskette durch das return unterbrochen wird.
Und das nächste zu schreiben, würde ja auch niemand wollen (weil es auch nicht gänzlich, z. B. hinsichtlich Erweiterungsmethoden, unterstützt wird):

private Func<IEnumerable<T>, IEnumerable<T>> Test
   = (x =>
x.Select(y => y.ShadowClone())
.Where(y != null))
.Reverse();

Gibt es so etwas nun schon? Ist das FP?

Antwort
von triopasi, 39

Wofür bitte sollte sowas zB gut sein??? Also warum sollte man das a++ da hinschreiben aber ignorieren?

return a++ ~ a^2;
Kommentar von KnorxyThieus ,

Okay, das Beispiel war schlecht gewählt ... dann mögest du dich doch bitte auf das zweite beziehen.

Keine passende Antwort gefunden?

Fragen Sie die Community