Sie befin­den sich hier:Start­sei­te/Blog/Redu­zie­ren redu­ziert Aufwände

Reduzieren reduziert Aufwände

Lese­zeit: 8 Minu­ten
Reduzieren reduziert Aufwände

Wer ger­ne kocht, weiß: Das Geheim­nis einer guten Soße liegt im Redu­zie­ren, durch das die geschmack­li­che Essenz stär­ker zu Tage tritt. Eine ähn­li­che Tech­nik gibt es bei der Funk­tio­na­len Pro­gram­mie­rung: Redu­cer oder Redu­ce-Funk­tio­nen redu­zie­ren Daten­men­gen auf ein­zel­ne Wer­te (z.B. Sum­men, Durch­schnit­te, Sta­tis­tik). Für vie­le FP-Anfän­ger sind Redu­cer ein Buch mit sie­ben Sie­geln. In die­sem Bei­trag möch­te ich zei­gen, dass sie eigent­lich ein sehr ein­fa­ches und nahe­lie­gen­des Kon­zept sind.

Wie schon bei der Ein­füh­rung in FP ver­wen­de ich für die Code­bei­spie­le Eli­xir, weil ich die­se Spra­che für ele­gant und selbst­er­klä­rend halte.

Wie summiert man ohne Schleifen und Variablen?

Den­ken Sie an eine ziem­lich ein­fa­che Auf­ga­be: Um eine Anzahl bestell­ter Arti­kel in einem Auf­trag zu ermit­teln, müs­sen Sie die bestell­te Anzahl aller Posi­tio­nen auf­ad­die­ren. Schau­en wir uns an, wie man ein sol­ches Pro­blem impe­ra­tiv gelöst hat, z. B. in Java:

In die Zwi­schen­ab­la­ge kopieren

Eine Men­ge Code! Bevor es gene­ri­sche Klas­sen und Itera­to­ren gab, wäre das noch mehr gewe­sen. Und noch dazu unsi­cher durch die Ver­wen­dung ver­än­der­ba­rer Zustän­de, schlecht test­bar usw. Den­noch ist zu befürch­ten, dass die Mehr­zahl der Soft­ware-Ent­wick­ler das immer noch für den rich­ti­gen Weg zur Lösung hält.

Eigent­lich möch­te man das Kon­zept „sum­mie­re die bestell­te Anzahl aller Posi­tio­nen“ doch auch ein­fa­cher im Code aus­drü­cken kön­nen. Schau­en wir, wie man das in FP schrei­ben könnte:

In die Zwi­schen­ab­la­ge kopieren

Das ist noch nicht der „rich­ti­ge“ bzw. ele­gan­tes­te Weg (der kommt wei­ter unten). Ich woll­te sie zunächst an redu­ce heranführen.

Zunächst fällt die völ­lig ande­re Code­struk­tur auf: FP arbei­tet statt mit Klas­sen mit Daten­struk­tu­ren und Funk­tio­nen, die sie umwan­deln. auftrag ist eine Daten­struk­tur, die den Auf­trag ent­hält. Im nächs­ten Schritt wird die­se durch postionen in eine Men­ge Posi­tio­nen trans­for­miert (wie, spielt hier kei­ne Rol­le, im ein­fachs­ten Fall sind die Posi­tio­nen wie bei der OOP-Klas­se Bestand­teil der Stuk­tur von auftrag). Anschlie­ßend wird für die­se Men­ge mit einer ent­spre­chen­den Funk­ti­on die Sum­me der bestell­ten Arti­kel berech­net. (Die Zuwei­sung zum Ali­as anzahl ist hier nur erfolgt, um es ana­log zum Java-Bei­spiel zu hal­ten, wäre in FP aber eigent­lich unüblich.)

Ent­schei­dend ist aber die Imple­men­tie­rung von summeBestellteAnzahl: Aus den sechs Zei­len des Bodys der Java-Funk­ti­on ist eine gewor­den: eine reduce-Funktion.

Reduce macht aus vielen Werten einen

Nur etwas Theo­rie. Schau­en wir uns die Funk­ti­ons­si­gna­tur von reduce an:

In die Zwi­schen­ab­la­ge kopieren

reduce erhält eine Men­ge des Daten­typs a (unse­re Posi­tio­nen) und trans­for­miert sie in einen Wert des Daten­typs b (hier Int). Sie macht das durch Akku­mu­la­ti­on („An-/Auf­häu­fung“). Dafür braucht sie zwei wei­te­re Para­me­ter: Den Initi­al­wert des Akku­mu­la­tors vom Typ b (bei Sum­men typi­scher­wei­se 0) und eine Akku­mu­la­tor­funk­ti­on. Die­se bekommt wie­der­um zwei Para­me­ter: ein Ele­ment der Men­ge vom Typ a, also hier jeweils eine Posi­ti­on, und den Wert des Akku­mu­la­tors. Ihre Auf­ga­be ist es, den Wert des Akku­mu­la­tors durch Akku­mu­lie­ren des Ele­ments zu ermit­teln (hier, indem sie die bestell­te Anzahl der jewei­li­gen Posi­ti­on zur bis­he­ri­gen Sum­me dazu addiert). Das wird viel­leicht deut­li­cher, wenn wir die Akku­mu­la­tor­funk­ti­on aus dem obi­gen Bei­spiel etwas kla­rer hinschreiben:

In die Zwi­schen­ab­la­ge kopieren

Die reduce-Funk­ti­on macht also (natür­lich) inhalt­lich das glei­che wie die Schlei­fen­im­ple­men­tie­rung in Java: Sie ruft rekur­siv die Akku­mu­la­tor­funk­ti­on mit den Ele­men­ten auf, bis die­se auf­ge­braucht ist, und ant­wor­tet dann den akku­mu­lier­ten Wert. Und sie macht das rein funk­tio­nal, ohne (unsi­che­re) Varia­blen und ohne über­flüs­si­gen Schreib­auf­wand – und wiederverwendbar.

Das ist noch nicht funktional genug

Wenn man schon etwas mehr FP-Erfah­rung hat, stört einen womög­lich, dass sowohl der Auf­ruf als auch die Funk­ti­on summeBestellteAnzahl nicht wirk­lich ele­gan­ter FP-Code sind. Man soll­te immer ver­su­chen, ato­ma­re Funk­tio­nen zu schrei­ben und wie­der­zu­ver­wen­den. Was wir eigent­lich schrei­ben wol­len, ist:

In die Zwi­schen­ab­la­ge kopieren

Hier sehen wir wie­der den Vor­teil von FP-Code: Aus­drucks­stär­ke. Die Pipe oben beschreibt näm­lich genau, wie laut Spe­zi­fi­ka­ti­on die Gesamt­an­zahl defi­niert ist: Nimm vom Auf­trag die Posi­tio­nen, von die­sen die bestell­te Anzahl und sum­mie­re das auf. Voi­la! Ein­fa­cher kann Code nicht sein. Schwie­ri­ger soll­te er nicht sein.

reduce ist so mächtig wie map

Dem einen oder ande­ren impe­ra­ti­ven Pro­gram­mie­rer mag das alles als zu umständ­lich erschei­nen, um bloß eine Sum­me zu berech­nen. Natür­lich wäre der Lern­auf­wand unver­hält­nis­mä­ßig, nur um ein paar Zei­len Code zu spa­ren. Aber gehen Sie mal vor ihrem geis­ti­gen Auge typi­sche Java- oder C++-Programme durch: 60–80% des Codes bestehen typi­scher­wei­se aus sol­chem impe­ra­ti­ven Boi­ler­p­la­te-Code wie Schlei­fen, Zäh­ler­va­ria­blen etc.

Der eigent­li­che Witz bei FP-Funk­tio­nen ist ja ihre viel­ge­stal­ti­ge Ein­setz­bar­keit, weil sie Funk­tio­nen als Para­me­ter erhal­ten kön­nen. (Sie erin­nern sich: Man spricht von „Funk­tio­nen höhe­rer Ord­nung“) Und das macht sie uni­ver­sell wie­der­ver­wend­bar in einem Maße, wie es OOP nie erreicht hat (weil jeder Wie­der­ver­wen­dungs­er­spar­nis immer auch Mehr­auf­wand wie abs­trak­te Klas­sen, Inter­faces u.ä. erfor­der­te, was den Code oft zusätz­lich unles­ba­rer mach­te). Hier als Bei­spiel drei sehr häu­fig ver­wen­de­te Funk­tio­nen zum Trans­for­mie­ren von Men­gen:

  • map kann eine Men­ge von Typ a in eine gleich­mäch­ti­ge Men­ge von Typ b umwan­deln und beö­tigt dazu eine Funk­ti­on a -> b (a und b kön­nen gleich sein)
  • filter kann von einer Men­ge eines Typs eine Unter­men­ge des glei­chen Typs bil­den und benö­tigt dazu ein soge­nann­tes Prä­di­kat, eine Funk­ti­on a -> Boolean
  • reduce trans­for­miert eine Men­ge von a auf einen Wert von b und benö­tig dazu zwei Para­me­ter, den Ini­tal­wert von b und die Akku­mu­la­tor­funk­ti­on (a, b) -> b

Wenn Sie die­ses Grund­prin­zip ver­in­ner­licht haben (und das geht ziem­lich schnell, wenn man nicht an Schlei­fen, Varia­blen und Boi­ler­p­la­te hängt), wer­den Sie ver­blüfft sein, wie häu­fig sich die­se funk­tio­na­len Lego-Bau­stei­ne in belie­bi­gen Kom­bi­na­tio­nen ein­set­zen las­sen (die o.g. Funk­ti­on sum: Enum Int -> Int lässt sich zum Sum­mie­ren jeder Men­ge von Zah­len ver­wen­den) und wie ein­fach man sel­ber wel­che dar­aus bau­en kann. Das ist die Magie der funk­tio­na­len Programmierung!

Wie leicht man eine Funk­ti­on wie reduce auch selbst imple­men­tie­ren kann, haben die­ses Mus­ter im ers­ten Bei­trag zur FP bereits als Lis­ten­re­kur­si­on kennengelernt:

reduce für eine Lis­te, einen Akku­mu­la­tor und eine Akku­mu­la­ti­ons­funk­ti­on ist

  • der Akku­mu­la­tor, wenn die Lis­te leer ist (Basis­fall)
  • reduce für den Rest der Lis­te mit dem Wert der Akku­mu­la­ti­ons­funk­ti­on für das ers­te Ele­ment und den bis­he­ri­gen Akku­mu­la­tor (gibt einen neu akku­mu­lier­ten Wert), wenn die Lis­te min­des­tens ein Ele­ment (head) und rest­li­che (tail) ent­hält
In die Zwi­schen­ab­la­ge kopieren

Hier­aus wird deut­lich, dass sich Enum.reduce weder für den Typ a noch b inter­es­siert, son­dern es kom­plett fnAcc über­lässt, die Ele­men­te in acc zu aggre­grie­ren. Das heißt: Natür­lich kann Typ b bei Enum.reduce eine belie­big kom­ple­xe Daten­struk­tur sein, z. B. eine Sta­tis­tik oder auch eine ande­re Men­ge. Kon­kre­te Bei­spie­le dafür wer­de ich mir für ein ande­res Mal aufheben.

Zum Abschluss daher hier nur ein wei­te­res ein­fa­ches Bei­spiel. Was es macht, soll­te selbst erklä­rend sein:

In die Zwi­schen­ab­la­ge kopieren

Hin­weis: Nor­ma­ler­wei­se hät­te man die­ses max auch als Lis­ten­re­kur­si­on imple­men­tie­ren kön­nen. Ich woll­te Ihnen hier nur zei­gen, wie man reduce pro­blem­los für ande­re Din­ge als Sum­men ver­wen­den kann.

Schreiben Sie Code, den Sie auch nächstes Jahr noch verstehen

Wie ich schon mehr­fach beton­te, ist FP kein Selbst­zweck, son­dern führt zu ein­fa­chem, siche­ren und aus­drucks­star­ken Code, der mas­siv die Pro­duk­ti­vi­tät der Soft­ware-Ent­wick­lung erhö­hen kann, denn: Boi­ler­p­la­te-Code wie der ein­gangs gezeig­te muss nicht nur geschrie­ben, son­dern auch getes­tet und gele­sen werden&bsp;– und erfor­dert häu­fig genug Debug­ging, weil mehr Code mehr Feh­ler­mög­lich­kei­ten bedeu­tet; wel­cher Pro­gram­mie­rer hat z. B. noch nie ver­se­hent­lich die Sum­men­va­ria­ble in der Schlei­fe neu defi­niert und sich dann gewun­dert, dass die äuße­re nicht auf­ad­diert wird?

Ich kann Sie nur ermu­ti­gen, sich all­mäh­lich an die­ses Para­dig­ma der Zukunft anzu­nä­hern. In den nächs­ten Jah­ren wird FP die impe­ra­ti­ve Pro­gram­mie­rung (inkl. OOP) ver­drängt haben (von der Java-Lega­cy abge­se­hen, die uns natür­lich als Bal­last erhal­ten blei­ben wird wie die COBOL-Sys­te­me, die in den 60er bis 80er Jah­ren ein­ge­führt wur­den). Das ist nicht nur wegen der Pro­duk­ti­vi­täts­vor­tei­le und des Fach­kräf­te­man­gels zwangs­läu­fig, son­dern auch eine tech­ni­sche Not­wen­dig­keit, weil impe­ra­ti­ve Pro­gram­me ein­fach für die Her­aus­for­de­run­gen mas­siv ver­teil­ter und neben­läu­fi­ger Sys­te­me unge­eig­net sind, wie Digi­ta­li­sie­rung und IoT sie erfordern.

Wenn Sie Hil­fe brau­chen oder sich ein­fach nur aus­tau­schen wol­len, spre­chen Sie mich bit­te jeder­zeit an oder benut­zen Sie die Kom­men­tar­funk­ti­on. Ich freue mich auf das Gespräch mit Ihnen.

Kommentare und Feedback gerne via Social Media:

Blei­ben Sie auf dem Lau­fen­den – mit dem monat­li­chen sepp.med Newsletter: