Warum nicht Funktionsparameter mit Anmerkungen versehen?

28

Um diese Frage beantworten zu können, nehmen wir an, dass die Mehrdeutigkeit eines Programmierers viel teurer ist als ein paar zusätzliche Tastenanschläge.

Warum sollte ich meinen Teamkollegen gestatten, ihre Funktionsparameter nicht zu kommentieren? Nehmen Sie den folgenden Code als Beispiel für einen weitaus komplexeren Code:

let foo x y = x + y

Eine kurze Betrachtung des Tooltips zeigt Ihnen nun, dass Sie mit F # festgelegt haben, dass x und y Ints sein sollen. Wenn Sie das beabsichtigt haben, ist alles in Ordnung. Aber ich weiß nicht, ob Sie das beabsichtigt haben. Was wäre, wenn Sie diesen Code erstellt hätten, um zwei Zeichenfolgen miteinander zu verknüpfen? Oder was ist, wenn ich denke, dass Sie wahrscheinlich Doppel hinzufügen wollten? Oder was ist, wenn ich nicht mit der Maus über jeden einzelnen Funktionsparameter fahren möchte, um dessen Typ zu bestimmen?

Nun nimm dies als Beispiel:

let foo x y = "result: " + x + y

F # geht nun davon aus, dass Sie wahrscheinlich beabsichtigt haben, Zeichenfolgen zu verketten. Daher werden x und y als Zeichenfolgen definiert. Als der arme Trottel, der Ihren Code verwaltet, könnte ich mir das ansehen und fragen, ob Sie vielleicht beabsichtigt haben, x und y (ints) zusammenzufügen und das Ergebnis dann zu UI-Zwecken an einen String anzuhängen.

Für solch einfache Beispiele könnte man es sicherlich loslassen, aber warum nicht eine Richtlinie mit expliziten Typanmerkungen erzwingen?

let foo (x:string) (y:string) = "result: " + x + y

Was schadet es, eindeutig zu sein? Sicher, ein Programmierer könnte die falschen Typen für das auswählen, was er versucht, aber zumindest weiß ich, dass sie es beabsichtigt haben, dass es nicht nur ein Versehen war.

Dies ist eine ernste Frage ... Ich bin noch sehr neu in F # und ebne den Weg für meine Firma. Die Standards, die ich übernehme, werden wahrscheinlich die Grundlage für die gesamte zukünftige F # -Codierung sein, eingebettet in das endlose Kopieren, von dem ich sicher bin, dass es die Kultur für die kommenden Jahre durchdringen wird.

Also ... gibt es etwas Besonderes an der Typinferenz von F #, das es zu einer wertvollen Funktion macht, sie beizubehalten und nur dann zu kommentieren, wenn es nötig ist? Oder machen es sich Experten zur Gewohnheit, ihre Parameter für nicht-triviale Anwendungen zu kommentieren?

JDB
quelle
Die kommentierte Version ist weniger allgemein gehalten. Aus dem gleichen Grund verwenden Sie in Signaturen vorzugsweise IEnumerable <> und nicht SortedSet <>.
Patrick
2
@Patrick - Nein, das ist es nicht, weil F # auf den String-Typ schließt. Ich habe die Signatur der Funktion nicht geändert, sondern nur explizit angegeben.
JDB
1
@Patrick - Und selbst wenn es wahr wäre, kannst du erklären, warum das eine schlechte Sache ist? Vielleicht ist die Unterschrift sollte weniger allgemein sein, wenn es das ist , was der Programmierer beabsichtigt hatte. Vielleicht verursacht die allgemeinere Signatur tatsächlich Probleme, aber ich bin nicht sicher, wie spezifisch der Programmierer sein wollte, ohne viel Nachforschungen anzustellen.
JDB
1
Ich denke, es ist fair zu sagen, dass Sie in funktionalen Sprachen die Allgemeinheit bevorzugen und im speziellen Fall Anmerkungen machen. Zum Beispiel, wenn Sie eine bessere Leistung wünschen und diese mithilfe von Tipping erhalten möchten. Wenn Sie kommentierte Funktionen überall verwenden, müssen Sie Überladungen für jeden bestimmten Typ schreiben, was im allgemeinen Fall möglicherweise nicht gewährleistet ist.
Robert Harvey

Antworten:

30

Ich verwende kein F #, aber in Haskell wird es als gute Form angesehen, (mindestens) Definitionen der obersten Ebene und manchmal auch lokale Definitionen zu kommentieren, obwohl die Sprache eine allgegenwärtige Typinferenz aufweist. Dies hat einige Gründe:

  • Lesen
    Wenn Sie wissen möchten, wie eine Funktion verwendet wird, ist es unglaublich nützlich, die Typensignatur zur Verfügung zu haben. Sie können es einfach lesen, anstatt zu versuchen, es selbst abzuleiten oder sich auf Tools zu verlassen, die es für Sie erledigen.

  • Refactoring
    Wenn Sie eine Funktion ändern möchten, können Sie durch eine explizite Signatur sicher sein, dass bei Ihren Transformationen die Absicht des ursprünglichen Codes erhalten bleibt. In einer vom Typ abgeleiteten Sprache werden Sie möglicherweise feststellen, dass stark polymorpher Code die Typprüfung durchführt, aber nicht das tut, was Sie beabsichtigt haben. Die Typensignatur ist eine „Barriere“, die Typinformationen an einer Schnittstelle konkretisiert.

  • Leistung
    In Haskell kann der abgeleitete Typ einer Funktion (über Typklassen) überladen sein, was einen Laufzeitversand implizieren kann. Bei numerischen Typen ist der Standardtyp eine Ganzzahl mit beliebiger Genauigkeit. Wenn Sie diese Funktionen nicht vollständig benötigen, können Sie die Leistung verbessern, indem Sie die Funktion auf den gewünschten Typ spezialisieren.

Bei lokalen Definitionen, letgebundenen Variablen und formalen Parametern für Lambdas stelle ich fest, dass Typensignaturen im Code normalerweise mehr kosten als der Wert, den sie hinzufügen würden. Ich empfehle Ihnen daher, bei der Codeüberprüfung auf Signaturen für Definitionen der obersten Ebene zu bestehen und an anderer Stelle nur nach vernünftigen Anmerkungen zu fragen .

Jon Purdy
quelle
3
Dies klingt nach einer sehr vernünftigen und ausgewogenen Reaktion.
JDB
1
Ich bin damit einverstanden, dass die Definition mit den explizit definierten Typen beim Refactoring hilfreich sein kann, aber ich finde, dass Unit-Tests auch dieses Problem lösen können, und weil ich der Meinung bin, dass Unit-Tests mit funktionaler Programmierung verwendet werden sollten, um sicherzustellen, dass eine Funktion wie geplant funktioniert, bevor die verwendet wird Wenn ich mit einer anderen Funktion arbeite, kann ich die Typen aus der Definition herausnehmen und darauf vertrauen, dass sie abgefangen werden, wenn ich eine Änderung vornehme. Beispiel
Guy Coder
4
In Haskell wird es als richtig angesehen, Ihre Funktionen mit Anmerkungen zu versehen, aber ich glaube, dass idiomatisches F # und OCaml dazu neigen, Anmerkungen wegzulassen, sofern dies nicht erforderlich ist, um Mehrdeutigkeiten zu beseitigen. Das soll nicht heißen, dass es schädlich ist (obwohl die Syntax für Anmerkungen in F # hässlicher ist als in Haskell).
KChaloux
4
@KChaloux In OCaml gibt es bereits Anmerkungen geben Sie in die Schnittstelle ( .mli) Datei (falls Sie eine schreiben, die Sie zu stark gefördert Typenannotationen neigen dazu , von Definitionen werden weggelassen , da sie redundant mit der Schnittstelle sein würde..
Gilles ' Hören Sie auf, böse
1
@Gilles @KChaloux in der Tat, dasselbe gilt für F # -Signaturdateien ( .fsi), mit einer Einschränkung: F # verwendet keine Signaturdateien für die Typinferenz. Wenn also etwas in der Implementierung nicht eindeutig ist, müssen Sie es dennoch erneut mit Anmerkungen versehen. books.google.ca/…
Yawar Amin
1

Jon gab eine vernünftige Antwort, die ich hier nicht wiederholen werde. Ich zeige Ihnen jedoch eine Option, die Ihre Anforderungen erfüllen könnte, und dabei wird eine andere Art von Antwort als Ja / Nein angezeigt.

In letzter Zeit habe ich mit Parsing mit Parser-Kombinatoren gearbeitet. Wenn Sie sich mit Parsing auskennen, wissen Sie, dass Sie in der ersten Phase in der Regel einen Lexer und in der zweiten Phase einen Parser verwenden. Der Lexer konvertiert Text in Token und der Parser konvertiert Token in einen AST. Jetzt, da F # eine funktionale Sprache ist und Kombinatoren kombiniert werden sollen, verwenden Parser-Kombinatoren die gleichen Funktionen sowohl im Lexer als auch im Parser. Wenn Sie jedoch den Typ für die Parser-Kombinator-Funktionen festlegen, können Sie diese nur verwenden lex oder zu analysieren und nicht beides.

Beispielsweise:

/// Parser that requires a specific item.

// a (tok : 'a) : ('a list -> 'a * 'a list)                     // generic
// a (tok : string) : (string list -> string * string list)     // string
// a (tok : token)  : (token list  -> token  * token list)      // token

oder

/// Parses iterated left-associated binary operator.


// leftbin (prs : 'a -> 'b * 'c) (sep : 'c -> 'd * 'a) (cons : 'd -> 'b -> 'b -> 'b) (err : string) : ('a -> 'b * 'c)                                                                                    // generic
// leftbin (prs : string list -> string * string list) (sep : string list -> string * string list) (cons : string -> string -> string -> string) (err : string) : (string list -> string * string list)  // string
// leftbin (prs : token list  -> token  * token list)  (sep : token list  -> token  * token list)  (cons : token  -> token  -> token  -> token)  (err : string) : (token list  -> token  * token list)   // token

Da der Code urheberrechtlich geschützt ist, werde ich ihn hier nicht einfügen, er ist jedoch bei Github erhältlich . Kopieren Sie es hier nicht.

Damit die Funktionen funktionieren, müssen sie mit den generischen Parametern belassen werden, aber ich füge Kommentare hinzu, die die abgeleiteten Typen in Abhängigkeit von den verwendeten Funktionen anzeigen. Dies macht es einfach, die Funktion für die Wartung zu verstehen, während die generische Funktion für die Verwendung belassen wird.

Guy Coder
quelle
1
Warum würden Sie die generische Version nicht in die Funktion schreiben? Würde das nicht klappen
SVICK
@svick Ich verstehe deine Frage nicht. Meinen Sie damit, dass ich die Typen aus der Funktionsdefinition herausgelassen habe, aber die generischen Typen zu den Funktionsdefinitionen hätte hinzufügen können, da die generischen Typen die Bedeutung der Funktion nicht ändern würden? Wenn ja, ist der Grund eher eine persönliche Präferenz. Als ich mit F # anfing, habe ich sie hinzugefügt. Als ich anfing, mit fortgeschritteneren Benutzern von F # zu arbeiten, zogen sie es vor, sie als Kommentare zu hinterlassen, da es einfacher war, den Code ohne die dortigen Signaturen zu ändern. ...
Guy Coder
Auf lange Sicht, wie ich sie als Kommentare zeige, hat das für alle funktioniert, sobald der Code funktioniert hat. Wenn ich anfange, eine Funktion zu schreiben, gebe ich die Typen ein. Sobald die Funktion funktioniert, verschiebe ich die Typensignatur in einen Kommentar und entferne so viele Typen wie möglich. Manchmal müssen Sie mit F # den Typ belassen, um die Inferenz zu erleichtern. Eine Weile habe ich damit experimentiert, den Kommentar mit let und = zu erstellen, damit Sie die Zeile zum Testen auskommentieren und anschließend erneut kommentieren können, aber sie sahen albern aus und das Hinzufügen von let und = ist nicht so schwierig.
Guy Coder
Wenn diese Kommentare Ihre Fragen beantworten, lassen Sie es mich wissen, damit ich sie in die Antwort verschieben kann.
Guy Coder
2
Wenn Sie also den Code ändern, ändern Sie die Kommentare nicht, was zu veralteter Dokumentation führt? Das klingt für mich nicht nach einem Vorteil. Und ich sehe nicht wirklich, wie unordentlicher Kommentar besser ist als unordentlicher Code. Für mich scheint dieser Ansatz die Nachteile der beiden Optionen zu kombinieren.
SVICK
-8

Die Anzahl der Fehler ist direkt proportional zur Anzahl der Zeichen in einem Programm!

Dies wird normalerweise als die Anzahl der Fehler angegeben, die proportional zu den Codezeilen sind. Wenn Sie jedoch weniger Zeilen mit der gleichen Codemenge haben, erhalten Sie die gleiche Anzahl an Fehlern.

Es ist zwar nett, explizit zu sein, aber alle zusätzlichen Parameter, die Sie eingeben, können zu Fehlern führen.

James Anderson
quelle
5
"Die Anzahl der Bugs ist direkt proportional zur Anzahl der Zeichen in einem Programm!" Also sollten wir alle auf APL umsteigen ? Zu knapp zu sein ist ein Problem, genauso wie zu wortreich zu sein.
SVICK
5
" Um diese Frage beantwortbar zu machen, nehmen wir an, dass die Kosten der Mehrdeutigkeit im Kopf eines Programmierers viel teurer sind als ein paar zusätzliche Tastenanschläge. "
JDB