2013-08-20 5 views
7

Ich habe in der letzten Zeit einen Teil meiner Freizeit damit verbracht, meinen Kopf rund um Haskell und funktionale Programmierung im Allgemeinen zu wickeln. Eines meiner größten Bedenken war (und ist immer noch) das, was ich als Unlesbarkeit von knappen, funktionalen Äußerungen empfinde (sei es in Haskell oder einer anderen Sprache).Funktional vs. Imperativ Stil in Python

Hier ist ein Beispiel. Ich habe gerade die folgende Transformation in einigen Code für die Arbeit:

def scrabble_score(word, scoretable):  
    score = 0 
    for letter in word: 
     if letter in scoretable: 
      score += scoretable[letter] 
    return score 

zu

def scrabble_score(word, scoretable): 
    return sum([scoretable.get(x, 0) for x in word]) 

Letzteres war viel befriedigender (und zu schreiben, wenn man bedenkt, dass ich mich derjenige bin, schrieb es, um auch zu lesen). Es ist kürzer und süßer und deklariert keine lästigen Variablen, die Kopfschmerzen bereiten könnten, wenn ich beim Schreiben des Codes irgendwelche Fehler machen würde. (BTW Ich realisiere, ich hätte die Methode get() des Dikters in der imperativ-Stil-Funktion verwendet haben, aber ich erkannte es, wie ich diese Transformation durchgeführt habe, so habe ich es so in dem Beispiel.)

Meine Frage ist: trotz all das, ist die letztere Version des Codes wirklich besser lesbar als der ehemalige? Es scheint ein monolithischer Ausdruck zu sein, der im Vergleich zu der früheren Version, in der man die Bedeutung des Blocks aus den kleineren, atomaren Stücken zusammenfassen kann, auf einmal gerupft werden muss. Diese Frage kommt von meiner Frustration mit dem Versuch, angeblich leicht verständliche Haskell-Funktionen zu dekodieren, sowie meiner Beharrlichkeit als TA für Erstsemester-CS-Studenten, dass einer der Hauptgründe, warum wir Code schreiben (der oft übersehen wird) ist kommunizieren Ideen mit anderen Programmierern/Informatikern. Ich bin nervös, dass der Terser-Stil nur nach Schreibcode klingt und daher den Zweck der Kommunikation und des Verständnisses von Code vereitelt.

+3

Nur ein Kommentar über Ihren Code in der zweiten Version: Ich glaube nicht, dass Sie muss eigentlich eine Liste machen. Sie können einfach sagen 'sum (scoretable.get (x, 0) für x in Wort)'. – pandubear

+0

Das Problem mit dieser Art von Vergleichen ist, dass Programmierer unabhängig davon, wie lesbar eine bestimmte Sprache und Stil "objektiv" ist (wie auch immer man es im objektiven Sinn definiert), * sehr gut * darin lesen, welche Sprachen und Stile sie auch wirklich lesen arbeiten mit. Ein ungewohnter Stil * immer * scheint weniger lesbar als das, was Sie gewohnt sind, ungeachtet des Verdienstes. – Ben

+0

Man könnte argumentieren, dass es _pythonic_ wäre, 'sum (score_table [Buchstabe] für Buchstabe in Wort) zu verwenden und' KeyError' zuzulassen, außer es wird erwartet, dass Buchstaben nicht in 'scatterbar' sind. –

Antwort

6

Meine Frage ist: trotz all dieser, ist die letztere Version des Codes wirklich lesbarer als die erste? Es scheint wie ein monolithischer Ausdruck, der auf einmal werden muss grokked

Ich würde sagen, es ist besser lesbar, wenn auch nicht unbedingt die meisten lesbar Option. Ja, es ist mehr Logik in einer Zeile, aber dies wird durch drei Fakten aufgewogen.

Zuerst können Sie immer noch geistig zerlegen es in kleinere Teile und verstehen diese einzeln vor der Kombination von ihnen. Ich sehe sofort, dass Sie summieren etwas (in diesem Stadium muss ich nicht wissen was Sie summieren). Ich sehe auch, dass Sie die Einträge von scoretable für jede letter erhalten, standardmäßig auf 0, wenn nicht vorhanden (in diesem Stadium muss ich nicht wissen, was dann mit dieser Liste passiert). Diese Dinge können unabhängig voneinander identifiziert und verstanden werden. Zweitens gibt es sehr wenige etwas unabhängige Teile, vielleicht zwei oder drei. Dies ist nicht unvernünftig zu verstehen, der "monolithische" Aspekt ist eher ein Problem, wenn Dutzende von Anliegen nebeneinander sitzen.

Schließlich ist der vermeintliche Vorteil der ersten Version,

im Vergleich zu der früheren Version, wo Sie die Möglichkeit haben, die Bedeutung des Blockes von den kleineren, mehr Atom Stücken aufzubauen.

ist auch ein Problem: Wenn ich zu lesen, sehe ich eine for-Schleife. Das ist nett, aber es sagt mir noch nichts (außer dass es eine Schleife gibt, die mir nicht hilft, die Logik zu verstehen, nur ihre Implementierung). Also muss ich diese Tatsache im Hinterkopf behalten und weiterlesen, um später zu klären, was diese Schleife bedeutet. Bis zu einem gewissen Grad gilt dies für jeden Code, aber es wird umso schlechter, je mehr Code vorhanden ist. Ebenso ist die score = 0 Zeile bedeutet nicht alles auf eigene Faust, es ist nur ein kleines Detail der Implementierung, die niemand wissen muss, um zu verstehen, dass diese Funktion Scrabble Score eines Wortes berechnet, indem die Punktzahl für jeden der Buchstaben des Wortes.

Wenn Sie Ihre Imperativ-Schleife lesen, müssen Sie mehrere veränderbare Variablen, den Steuerungsfluss usw. verfolgen, um herauszufinden, was passiert, und die Teile zusammensetzen. Darüber hinaus sind einfach mehr Zeichen zu lesen, und daher würde es länger dauern, es zu verstehen, selbst wenn das Verständnis in jedem Fall augenblicklich wäre. Sie müssen diese Kosten berücksichtigen. Was immer Sie gewinnen könnten, wenn Sie jede Zeile einfacher hätten, geht zu diesem Zweck verloren.

Das heißt, die folgende Version ist wohl cleaner, gerade weil sie die beiden Teile des Algorithmus in einzelne Linien aufspaltet:

def scrabble_score(word, scoretable): 
    letter_scores = [scoretable.get(letter, 0) for letter in word] 
    return sum(letter_scores) 

Aber der Unterschied ist eher klein, es ist eher subjektiv. Beide sind in Ordnung und viel besser als Ihre erste Version. (Oh, noch etwas: Ich würde einen Generatorausdruck anstelle eines Listenverständnisses verwenden, ich behielt nur das Listenverständnis, um Rauschen zu minimieren.)

1

Diese Dinge sind in erster Linie eine Frage des Geschmacks. Es sollte daran erinnert werden, dass der Geschmack selbst eine Frage der Bildung und Erfahrung ist - Sie finden einen längeren, imperativen Code, der lesbarer ist, denn das ist das Einzige, was Sie (bisher) gelernt haben zu lesen.

Ich finde Ihre zweite Form besser lesbar, weil ich an die Form des funktionalen Stils gewöhnt bin, und ich verstehe alle Elemente darin. Ich finde häufig prägnanten Haskell-Code nicht entzifferbar, weil ich nicht alle Operatoren gelernt habe.

Die praktische Lösung scheint mir: (a) Kommentare zu verwenden (b) etwas übermäßig schlaues zu vermeiden (c) alles zu erklären, was Sie tun möchten (d) prägnant und nicht eingerückt ist normalerweise einfacher zu lesen als umfangreich und tief eingerückt; und (e) erinnere dich an dein Publikum.

Um diese Prinzipien auf diesen Fall anzuwenden: In Python würden Sie erwarten, dass Dinge, die durch einfache funktionale Operationen ausgedrückt werden können, auf diese Weise ausgedrückt werden und Ihre Zielgruppe damit vertraut sein sollte. Ihr Code muss in jedem Fall einen Kommentar enthalten, um zu erklären, dass Sie ungültige (unsortierte) Zeichen ignorieren möchten (oder nicht).

1

Ich denke, der prägnante Stil ist nett, weil Sie es in Fällen wie diesem frei erweitern können, ohne viel vertikalen Raum auf Kontrollstrukturen zu verschwenden, wie für ein if in der ersten. Zum Beispiel

Ich finde dies am besten lesbar, obwohl es ein paar Zeilen mehr braucht. Ich würde dies im deklarativen Stil sehen.

+0

-1 Dies ist zutiefst unidiomatisch und kombiniert somit die schlechteste aller Welten. Unidiomatische Verwendung ist das, was der funktionalen Programmierung einen schlechten Namen gibt. – Marcin

+0

"Schlimmste aller Welten" Ich schätze Ihre Begeisterung, aber dieser Kommentar ist überhaupt nicht hilfreich. Was ist daran unidiomatisch? Es ist das Python-Gegenstück zur Verwendung von Let und Where in Haskell. –

+1

Die Tatsache, dass Sie es durch Bezugnahme auf Haskell-Idiome verteidigen, zeigt das Problem: Dies ist kein idiomatischer Python. Es spielt keine Rolle, dass es sich um idiomatischen Haskell handeln könnte. Es ist unidiomatisch, weil das übliche Python-Idiom zum Zuordnen eines Ausdrucks ein Verständnis ist, keine lokale Funktion. – Marcin