Herr Specht: [] + "" bzw. [] in array.reduce

Hallo, Disclaimer:

eingangs die Background Story, wer sich nur für des Pudels Kern interessiert, dem sei es gestattet, zum zweiten Code Snippet fortzufahren.

Bin bei der Reduce Methode auf eine [für mich] interessante Eigenheit von JavaScript gestoßen.

Ursprünglich wollte ich, dass ALLE Array Elements, die durch reduce geschleust werden, behandelt werden, bevor sie dem Accumulator hinzugefügt werden, und nicht dass das allererste unbehandelt als acc hinterlegt wird (das allerdings nur als Background Info).

Hab dann mit "" als Ausgangspunkt experimentiert, stolperte allerdings über die Erkenntnis, dass auch [] das gewünschte Resultat liefert:

function emptyArrTest(arr) {
	return arr.reduce((acc, cur) => {
		// do some amazing! jazz!! with cur (also the very first one)
		acc = acc + cur;
		return acc;
	}, []); // oder "", liefert beides dasselbe Resultat! :O
}

Vereinfacht passiert hier in Wahrheit ja sozusagen folgendes:

[] + cur + cur + cur // bis alle durchlaufen sind

i.e., z.B. "ein unglaublich toller(!) String":

[] + "ein unglaublich toller(!) String"

...logge ich das in die Konsole, ergibt das

ein unglaublich toller(!) String

AAAAAAAAABER

{} + "ein unglaublich toller(!) String"

...ein Objekt wird NICHT ignoriert, hier schnauzt mich die Konsole mit

[object Object]ein String

an!

Warum?

UND: ist das Good Practice, einer Reduce Methode in diesem Fall ein leeres Array zu übergeben?

(Kenne ich das von React - z.B. useEffect mit [] als Argument?)

Es dankt euch recht, Herr Specht.

  1. [] + "ein unglaublich toller(!) String"
    

    ...logge ich das in die Konsole, ergibt das

    ein unglaublich toller(!) String
    

    Es wird noch besser:

    console.log(typeof ([] + 5));
    

    Ergibt die Ausgabe: string. Ich vermute(!), dass das damit zu tun hat, dass Strings intern zum Teil wie Arrays behandelt werden und das Typensystem hier an seine Grenzen kommt.

    TypeScript funktioniert da intuitiver. Da ergibt obiger Code die Fehlermeldung Operator '+' cannot be applied to types 'never[]' and 'number'.ts(2365).

    1. Hallo Random2356,

      TypeScript ist, wie der Name sagt, eine Sprache mit getypten Variablen, die zur Ausführung nach JavaScript compiliert wird. Die von Dir genannte Meldung kommt vom Compiler, nicht von der Runtime.

      JavaScript-Variablen sind ungetypt und dementsprechend veranstaltet der JS Interpreter eine MENGE, um damit irgendwie klarzukommen.

      Ich finde im Selfwiki keinen Artikel über die Javascript-Konvertierungsregeln - und im Netz finde ich vor allem Hinweise darauf, wie wild diese Konvertierung sein kann.

      Grundsätzlich ist es so, dass JavaScript sich bei der Ausführung von mathematischen Operatoren eher ein Bein abbeißt als eine Fehlermeldung zu werfen, was überraschende Folgen haben kann. Versucht man, das in der JavaScript Spec nachzuvollziehen, begibt man sich auf einen abenteuerlichen Abstieg in die Tiefen der Grammatikproduktionen und primitiven Operationen - darauf gehe ich jetzt nicht ein. Unter anderem deshalb, weil ich bei diesem Abstieg an entscheidender Stelle die richtige Trittstufe nicht gefunden habe.

      Jedenfalls wird bei [] + 5 aus dem leeren Array der Wert 0 und bei [] + "5" wird es der Leerstring. Edith sagt: Rolf, du Dummerchen

      Jedenfalls macht der Plus-Operator aus [] einen Leerstring. Das ist ein Sonderfall für die allgemeine toString-Konvertierung. [1,2,3] + 5 wird bspw. zu dem String "1,2,35"

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Jedenfalls wird bei [] + 5 aus dem leeren Array der Wert 0 und bei [] + "5" wird es der Leerstring.

        Rolf

        Stimmt nicht ganz - würde das leere Array als Wert 0 aufgelöst werden, müsste das Endergebnis 5 vom Typ number sein - beide Fälle ergeben aber einen string.

        Zumindest []++ lässt sich JavaScript nicht bieten 😂

        1. Hallo Herr Specht,

          bitte zu entschuldigen. Da habe ich wohl nicht nur eine Trittstufe verpasst, sondern bin gleich ganz abgestürzt.

          Ich habe das Posting angepasst.

          Jetzt kapiere ich auch Abschnitt 7.1.1.1 der Spec. Er probiert beim Array zunächst valueOf. Das liefert das Array selbst, das ist ein Object, deshalb verwendet er das nicht und fährt mit toString fort. Und arr.toString() entspricht arr.join(','). Und schon passt die Spec zum beobachteten Verhalten 😀.

          []++ funktioniert aus einem anderen Grund nicht, das ist der Post-Inkrement Operator und DER verlangt eine LeftHandSide Expression. Der darf man aber auch nicht jeden Dutchie weitergeben (Hä?), ein Array funktioniert nur als LeftHandSide Expression wenn ein Pattern Assignment gemacht wird (z.B. [a,b,c] = [1,2,3] - aber [] = [] funktioniert auch, links und rechts ist leer, Pattern stimmt).

          Rolf

          --
          sumpsi - posui - obstruxi
      2. Hallo Random2356,

        TypeScript ist, wie der Name sagt, eine Sprache mit getypten Variablen,

        Und trotzdem meckert der TypeScript-Compiler, im Gegensatz zum gestrigen Beispiel [] + 5 hierbei nicht:

        console.log(typeof (5 + '5'));
        
        1. Hallo Random2356,

          tja. Mein Hinweis auf getypte Variablen war da wohlfeil, aber nicht sachgerecht.

          Wie auch immer - das ist ein Grenzfall für TS und warum er sich über [] + 5 aufregt, wo doch [] zu "" konvertiert und ""+5 erlaubt ist, weiß nur Papa Anders.

          Aber er meckert genauso, wenn man [] in eine Variable steckt und die als number[], any[] oder unknown[] deklariert. Er will also partout kein Array zu einer Zahl addieren. Dazu finde ich nichts im Handbuch, und da die Spec seit Jahren ungepflegt ist, guck ich dort erst gar nicht rein. TypeScript war für mich immer ein Ratespiel, weshalb ich es bisher verschmäht habe.

          Rolf

          --
          sumpsi - posui - obstruxi
        2. Hallo miteinander,

          TypeScript ist, wie der Name sagt, eine Sprache mit getypten Variablen,

          dieser Satz ließ mich stutzen. "getypt"? Klingt eigenartig.

          Und trotzdem meckert der TypeScript-Compiler, im Gegensatz zum gestrigen Beispiel [] + 5 hierbei nicht:

          console.log(typeof (5 + '5'));
          

          Das ist eine Frage der Definition. Gibt die Spezifikation von Typescript[1] her, dass Strings implizit in Zahlen konvertiert werden, wenn der Kontext das erfordert? Dann wäre obige Anweisung in Ordnung und müsste Number ausgeben - auch wenn das dem Prinzip von eindeutigen Datentypen ein bisschen widerspricht.

          In C gibt es ja auch unterschiedliche Integertypen, die sich in der Wortbreite und im Vorhandensein eines Vorzeichens unterscheiden. Aber da sie semantisch gesehen alle typgleich sind, sind sie auch untereinander zuweisungskompatibel. Auch wenn manche Compiler maulen, wenn man signed und unsigned mischt ...

          Einen schönen Tag noch
           Martin

          --
          Wer nicht genießt, wird ungenießbar.
          (Mottospruch auf einem T-Shirt)

          1. die ich nicht kenne ↩︎

  2. Hallo Herr Specht,

    UND: ist das Good Practice, einer Reduce Methode in diesem Fall ein leeres Array zu übergeben?

    Klare Antwort: JEIN!

    Du musst einen Startwert übergeben, der zu den Inhalten des zu reduzierenden Arrays und der gewünschten Reduktionsoperation passt. Und natürlich gibt es Fälle, wo Du ohne Startwert besser bedient bist. Das ist dann von Fall zu Fall zu entscheiden und ich tue jetzt einfach mal so, als sei die Entscheidung für den Startwert gefallen, auch wenn die Beispiel das nicht nahelegen 😉

    Wenn Du ein Array aus Strings hast und diese mit reduce zu einem langen String verketten willst, sollte der Startwert ein leerer String sein.

    Wenn Du ein Array aus Zahlen hast und diese mit reduce aufsummieren willst, sollte der Startwert die 0 sein.

    Wenn Du ein Array aus Arrays hast und diese mit reduce zu einem langen Array zusammenhängen willst, sollte der Startwert ein leeres Array sein.

    Ein leeres Array als Startwert zu übergeben, wenn Du eigentlich Zahlen im Array aufzummieren willst, ist aber definitiv worst practice.

    Rolf

    --
    sumpsi - posui - obstruxi