Eine Sprache, die auf einer begrenzten Anzahl von Argumenten basiert, die an Funktionen übergeben werden

16

Die Idee ist von der Tatsache inspiriert, dass Operatoren wie +, -,% usw. als Funktionen mit einem oder zwei übergebenen Argumenten und ohne Nebenwirkungen angesehen werden können. Angenommen, ich oder jemand anderes schreibt eine Sprache, die die Übergabe von mehr als zwei Argumenten verhindert und auch nur über den Rückgabewert funktioniert:

a) Würde eine solche Sprache zu einem einfacheren Verständnis von Code führen?

b) Wäre der Ablauf des Codes klarer? (In mehrere Schritte gezwungen, mit möglicherweise weniger versteckten Interaktionen)

c) Würden die Einschränkungen die Sprache für komplexere Programme übermäßig sperrig machen?

d) (Bonus) sonstige Kommentare zu Vor- / Nachteilen

Hinweis:

Es müssten noch zwei Entscheidungen getroffen werden - die erste ist, ob Benutzereingaben außerhalb von main () oder dessen Äquivalent zulässig sind und welche Regeln für die Übergabe von Arrays / Strukturen gelten. Wenn beispielsweise eine einzelne Funktion mehrere Werte hinzufügen soll, kann die Einschränkung umgangen werden, indem sie in einem Array zusammengefasst wird. Dies könnte gestoppt werden, indem nicht zugelassen wird, dass ein Array oder eine Struktur mit sich selbst interagiert. Auf diese Weise können Sie beispielsweise jede Zahl abhängig von ihrer Position durch einen anderen Betrag dividieren.


quelle
4
Hallo. Vor- und Nachteile-Listen neigen dazu, schlechte Antworten zu geben. Können Sie Ihre Frage auf irgendeine Weise umformulieren, um die benötigten Informationen auch in einem anderen Format zu erhalten?
MetaFight
22
Ihre Überlegungen ergeben für mich nicht einmal einen Sinn. Einige Funktionen haben nur wenige Argumente. Beschränken wir also alle Funktionen. Wenn man willkürliche Beschränkungen vorschlägt, gibt es normalerweise einen Grund, etwas zu gewinnen. Ich kann nicht sehen, was dir das bringen könnte.
2
Nicht, dass irgendetwas an 'Was-wäre-wenn'-Fragen von Natur aus falsch ist (obwohl sie manchmal schwer zu beantworten sind, wie @MetaFight sagte), aber selbst Sie, die an die Sache gedacht und sich genug um eine Frage gekümmert haben, können eine nicht wirklich benennen profitieren, dann bin ich mir ziemlich sicher, dass meine anfängliche Reaktion von "Was? Nein! Das ist dumm, warum würdest du das tun?" richtig ist.
6
Es gibt eine ganze Reihe von Sprachen, die nur ein einziges Argument pro Funktion zulassen: alles, was auf der Lambda-Rechnung basiert. Das Ergebnis ist in der Regel eine Funktion , um eine einzige Liste Argument nehmen, oder eine Funktion , eine Funktion zurückkehrt, die das nächste Argument , bis alle Argumente verarbeitet wurden: result = f(a)(b)…(z). Dies ist in der ML-Sprachfamilie wie Haskell der Fall, aber auch konzeptionell in anderen Sprachen wie Lisp, JavaScript oder Perl.
Amon
3
@Orangesandlemons: Okay, dann kann ich eine beliebige Anzahl von Ganzzahlen innerhalb einer einzelnen Ganzzahl codieren, indem ich nur multipliziere und addiere (zum Codieren) und dividiere und subtrahiere (zum Decodieren). Sie müssen also auch ganze Zahlen oder zumindest Multiplikation, Addition, Division und Subtraktion verbieten. (Eine Konsequenz der Leistungsfähigkeit des Programmierens ist, dass Sie fast alles mit fast allem codieren können, und daher ist das Einschränken von Dingen wirklich sehr, sehr schwierig. Im Allgemeinen "beschränken" Einschränkungen eigentlich nichts, sie ärgern nur Programmierer.)
Jörg W Mittag

Antworten:

40

Robert C. Martin empfiehlt in seinem Buch "Clean Code" nachdrücklich die Verwendung von Funktionen mit maximal 0, 1 oder 2 Parametern, sodass es zumindest einen erfahrenen Buchautor gibt, der der Meinung ist, dass Code durch diesen Stil sauberer wird (was er jedoch ist) sicherlich nicht die ultimative Autorität, und seine Meinungen sind umstritten).

Wo Bob Martin ist IMHO richtig ist: Funktionen mit 3 oder mehr Parametern sind oft Indikatoren für einen Code-Geruch. In vielen Fällen können die Parameter zu einem kombinierten Datentyp zusammengefasst werden, in anderen Fällen kann dies ein Indikator dafür sein, dass die Funktion einfach zu viel bewirkt.

Ich halte es jedoch nicht für eine gute Idee, eine neue Sprache dafür zu erfinden:

  • Wenn Sie eine solche Regel wirklich im gesamten Code durchsetzen möchten, benötigen Sie lediglich ein Codeanalyse-Tool für eine vorhandene Sprache. Sie müssen hierfür keine komplett neue Sprache erfinden (beispielsweise könnte für C # wahrscheinlich so etwas wie 'fxcop' verwendet werden ).

  • Manchmal ist das Kombinieren von Parametern zu einem neuen Typ einfach nicht der Mühe wert, oder es wird zu einer rein künstlichen Kombination. Siehe zum Beispiel diese File.OpenMethode aus dem .Net-Framework. Es werden vier Parameter benötigt, und ich bin mir ziemlich sicher, dass die Designer dieser API dies absichtlich getan haben, da sie dachten, dass dies der praktischste Weg wäre, die verschiedenen Parameter für die Funktion bereitzustellen.

  • Es gibt manchmal reale Szenarien, in denen mehr als zwei Parameter die Dinge aus technischen Gründen vereinfachen (z. B. wenn Sie eine 1: 1-Zuordnung zu einer vorhandenen API benötigen, bei der Sie an die Verwendung einfacher Datentypen gebunden sind und unterschiedliche nicht kombinieren können Parameter in ein benutzerdefiniertes Objekt)

Doc Brown
quelle
16
Der Geruch mit mehreren Parametern ist oft, dass die verschiedenen Parameter tatsächlich zusammengehören. Nehmen Sie zum Beispiel die Berechnung des Body Mass Index, BMI. Es ist eine Funktion der Länge und des Gewichts einer Person. f (Länge, Gewicht), aber diese beiden Parameter gehören wirklich zusammen, weil Sie diese Berechnung jemals mit der Größe einer Person und dem Gewicht einer anderen Person durchführen würden? Um dies besser darzustellen, würde man f (Person) erhalten, wobei die Person eine Schnittstelle für Gewicht und Länge haben kann.
Pieter B
@PieterB: natürlich siehe mein edit.
Doc Brown
5
Winziger, winziger Sprachnitpick: "könnte auf einen Codegeruch hinweisen" Ist ein Codegeruch nicht per Definition nur ein Hinweis darauf, dass Sie etwas überdenken sollten, auch wenn Sie den Code letztendlich nicht ändern? Ein Codegeruch würde also nicht "angezeigt". Wenn ein bestimmter Aspekt auf die Möglichkeit eines Problems hinweist, handelt es sich um einen Codegeruch. Nein?
jpmc26
6
@ jpmc26: Aus einer anderen Sicht ist ein Code-Geruch ein mögliches Problem, nicht die Angabe eines ;-) Es hängt alles von der genauen Definition des Code-Geruchs ab (und wenn er erst einmal riecht, ist er schlecht geworden, oder? ?)
hoffmale
3
@PieterB Tut das eigentlich jemand? Jetzt müssen Sie jedes Mal eine neue Personeninstanz erstellen, wenn Sie nur einen BMI mit zwei beliebigen Werten berechnen möchten. Sicher, wenn in Ihrer Anwendung bereits Personen verwendet werden und Sie häufig f (person.length, person.height) ausführen, können Sie dies ein wenig bereinigen, aber das Erstellen neuer Objekte speziell für Gruppenparameter scheint zu viel zu sein.
Lawyerson
47

Es gibt viele Sprachen, die bereits so funktionieren, z. B. Haskell. In Haskell verwendet jede Funktion genau ein Argument und gibt genau einen Wert zurück.

Es ist immer möglich, eine Funktion, die n Argumente akzeptiert, durch eine Funktion zu ersetzen, die n-1 Argumente akzeptiert und eine Funktion zurückgibt, die das letzte Argument akzeptiert. Wenn Sie dies rekursiv anwenden, ist es immer möglich, eine Funktion, die eine beliebige Anzahl von Argumenten akzeptiert, durch eine Funktion zu ersetzen, die genau ein Argument akzeptiert. Und diese Transformation kann mechanisch durch einen Algorithmus durchgeführt werden.

Dies nennt man Frege-Schönfinkeling, Schönfinkeling, Schönfinkel-Currying oder Currying, nach Haskell Curry, der es in den 1950er Jahren ausgiebig erforschte, Moses Schönfinkel, der es 1924 beschrieb, und Gottlob Frege, der es 1893 ankündigte.

Mit anderen Worten, die Beschränkung der Anzahl der Argumente hat genau keine Auswirkung.

Jörg W. Mittag
quelle
2
Das ist natürlich der Fall, wenn Sie die Rückgabe von Funktionen zulassen. Auch wenn dies nicht der Fall ist, müssen Sie lediglich die nächste Funktion im Hauptprogramm aufrufen. Das heißt, Sie könnten 1 + 1 + 1 haben, wobei die erste Addition eine Funktion ist, die eine Funktion für eine zusätzliche Addition zurückgibt, oder sie kann einfach zweimal aufgerufen werden. Der letztere Stil wäre theoretisch sauberer, oder irre ich mich?
5
"hat genau keine Auswirkung" - Die Frage des OP lautete, ob die Lesbarkeit des Codes steigen oder sinken würde, und ich nehme an, Sie behaupten nicht, dass eine solche Entwurfsentscheidung für eine Sprache keine Auswirkung darauf hat, oder?
Doc Brown
3
@DocBrown Mit anständiger Leistung und Bedienerüberlastung kann ich eine Curry-Sprache verwenden und sie wie eine Sprache ohne Curry aussehen lassen. Beispiel: f *(call_with: a,b,c,d,e) Überladen call_with :, um eine Kette zu beginnen, die Kette ,zu verlängern und *auf der LHS aufzurufen, findem jeder Inhalt der Kette einzeln übergeben wird. Ein ausreichend schwaches System zum Überladen von Operatoren macht die Syntax nur umständlich, aber das liegt vor allem am System zum Überladen von Operatoren.
Yakk
Tatsächlich reduziert Haskells Currying eine Funktion mit n Argumenten auf eine Funktion mit einem Argument, das eine andere Funktion mit n - 1 Argumenten zurückgibt.
Ryan Reich
2
@RyanReich Sie haben Recht, dass Sie einen "Geist" der "Anzahl der Argumente" einer Haskell-Funktion in ihrer Typensignatur sehen können. Es ist eher ein Geist als eine echte Signatur, da Sie im Allgemeinen nicht wissen können, ob die letzte Typvariable in der Signatur auch ein Funktionstyp ist. Auf jeden Fall macht dieser Geist, der dort ist, die Tatsache nicht ungültig, dass in Haskell alle Funktionen 1 Argument annehmen und entweder einen Nichtfunktionswert oder eine andere Funktion zurückgeben, die ebenfalls 1 Argument annimmt. Dies ist in die Assoziativität von -> eingebaut: a-> b-> c ist a -> (b-> c). Du liegst hier also meistens falsch.
Ian
7

Ich habe in den letzten Wochen einige Zeit damit verbracht, die Computersprache J zu lernen. In J ist so ziemlich alles ein Operator, so dass Sie nur "Monaden" (Funktionen mit nur einem Argument) und "Dyaden" (Funktionen mit genau zwei Argumenten) erhalten. Wenn Sie weitere Argumente benötigen, müssen Sie diese entweder in einem Array oder in "Feldern" angeben.

J kann sehr prägnant sein, aber wie sein Vorgänger APL kann es auch sehr kryptisch sein - dies ist jedoch hauptsächlich das Ergebnis des Ziels des Entwicklers, mathematische Prägnanz zu emulieren. Es ist möglich, ein J-Programm lesbarer zu machen, indem Namen anstelle von Zeichen zum Erstellen von Operatoren verwendet werden.

Alpheus
quelle
Ah, so können Arrays mit sich selbst interagieren.
5

Eine Sprache, die darauf basiert, wie sie den Entwickler einschränkt, hängt von der Annahme ab, dass der Sprachentwickler die Bedürfnisse jedes Programmierers besser versteht als der Programmierer diese Bedürfnisse selbst. Es gibt Fälle, in denen dies tatsächlich gültig ist. Beispielsweise werden die Einschränkungen für die Multithread-Programmierung, die eine Synchronisation unter Verwendung von Mutexen und Semaphoren erfordern, von vielen als "gut" angesehen, da die meisten Programmierer die zugrunde liegenden maschinenspezifischen Komplexitäten, die diese Einschränkungen vor ihnen verbergen, überhaupt nicht kennen. Ebenso möchten nur wenige die Nuancen von Multithread-Garbage-Collection-Algorithmen vollständig erfassen. Eine Sprache, mit der Sie den GC-Algorithmus einfach nicht brechen können, wird einer vorgezogen, die einen Programmierer zwingt, sich zu vieler Nuancen bewusst zu werden.

Sie müssten ein gültiges Argument dafür vorbringen, warum Sie als Sprachentwickler das Weitergeben von Argumenten so viel besser verstehen als die Programmierer, die Ihre Sprache verwenden. Ich denke, das wäre ein schwieriges Argument.

Sie müssen auch wissen , dass Programmierer wird Ihre Einschränkungen umgehen. Wenn sie drei oder mehr Argumente benötigen, verwenden sie Techniken wie das Currying, um sie in Aufrufe mit weniger Argumenten umzuwandeln. Dies geht jedoch oft zu Lasten der Lesbarkeit, anstatt sie zu verbessern.

Die meisten Sprachen, die ich mit dieser Art von Regel kenne, sind Esolangs, Sprachen, die demonstrieren sollen, dass Sie tatsächlich mit einem begrenzten Satz von Funktionen arbeiten können. Insbesondere die Esolangs, bei denen jedes Zeichen ein Opcode ist, neigen dazu, die Anzahl der Argumente zu begrenzen, einfach weil sie die Liste der Opcodes kurz halten müssen.

Cort Ammon - Setzen Sie Monica wieder ein
quelle
Das ist die beste Antwort.
Jared Smith
1

Sie benötigen zwei Dinge:

  • Schließung
  • Zusammengesetzter Datentyp

Ich werde ein mathematisches Beispiel hinzufügen, um die Antwort von Jörg W. Mittag zu erklären .

Betrachten Sie die Gaußsche Funktion .

Eine Gaußsche Funktion hat zwei Parameter für ihre Form, nämlich den Mittelwert (Mittelpunkt der Kurve) und die Varianz (bezogen auf die Impulsbreite der Kurve). Zusätzlich zu den beiden Parametern muss der Wert der freien Variablen angegeben xwerden, um diese auszuwerten.

Im ersten Schritt werden wir eine Gaußsche Funktion entwerfen, die alle drei Parameter verwendet, nämlich den Mittelwert, die Varianz und die freie Variable.

Im zweiten Schritt erstellen wir einen zusammengesetzten Datentyp, der den Mittelwert und die Varianz in einer Sache kombiniert.

Im dritten Schritt erstellen wir eine Parametrisierung der Gauß-Funktion, indem wir einen Abschluss der Gauß-Funktion erstellen, die an den zusammengesetzten Datentyp gebunden ist, den wir im zweiten Schritt erstellt haben.

Schließlich bewerten wir den im dritten Schritt erstellten Abschluss, indem wir den Wert der freien Variablen xan ihn übergeben.

Die Struktur ist daher:

  • Auswerten (Berechnung)
    • ParameterizedGaussian (Abschluss: die Formel plus einige gebundene Variablen)
      • GaussianParameters (zusammengesetzter Datentyp)
        • Mittelwert)
        • Varianz (Wert)
    • X (der Wert der freien Variablen)
rwong
quelle
1
  1. In nahezu jeder Programmiersprache können Sie einen Typ von Liste, Array, Tupel, Datensatz oder Objekt als einziges Argument übergeben. Es ist nur der Zweck, andere Elemente zu halten, anstatt sie einzeln an eine Funktion zu übergeben. Einige Java-IDEs haben sogar die Funktion " Parameterobjekt extrahieren ", um genau das zu tun. Intern implementiert Java eine variable Anzahl von Argumenten, indem es ein Array erstellt und übergibt.

  2. Wenn Sie wirklich das tun möchten, wovon Sie sprechen, müssen Sie sich die Lambda-Rechnung ansehen. Es ist genau das, was Sie beschreiben. Sie können im Web danach suchen, aber die Beschreibung, die für mich Sinn machte, war in Typen und Programmiersprachen .

  3. Schauen Sie sich die Programmiersprachen Haskell und ML an (ML ist einfacher). Sie basieren beide auf der Lambda-Rechnung und haben konzeptionell nur einen Parameter pro Funktion (wenn Sie ein wenig schielen).

  4. Josh Blochs Punkt 2 lautet: "Betrachten Sie einen Builder, wenn Sie mit vielen Konstruktorparametern konfrontiert werden." Sie können sehen, wie ausführlich dies ist , aber es ist eine Freude, mit einer API zu arbeiten, die auf diese Weise geschrieben wurde.

  5. Einige Sprachen haben benannte Parameter. Dies ist ein weiterer Ansatz, um die Navigation in umfangreichen Methodensignaturen zu vereinfachen. Kotlin hat zum Beispiel Argumente genannt .

GlenPeterson
quelle