diff --git a/de/tutorials/scala-for-java-programmers.md b/de/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..ca28395c75 --- /dev/null +++ b/de/tutorials/scala-for-java-programmers.md @@ -0,0 +1,634 @@ +--- +layout: overview +title: Ein Scala Tutorial für Java Programmierer +overview: scala-for-java-programmers + +disqus: true +multilingual-overview: true +language: de +--- + +Von Michel Schinz und Philipp Haller. +Deutsche Übersetzung von Christian Krause. + +## Einleitung + +Dieses Tutorial dient einer kurzen Vorstellung der Programmiersprache Scala und deren Compiler. Sie +ist für fortgeschrittene Programmierer gedacht, die sich einen Überblick darüber verschaffen wollen, +wie man mit Scala arbeitet. Grundkenntnisse in Objekt-orientierter Programmierung, insbesondere +Java, werden vorausgesetzt. + +## Das erste Beispiel + +Als erstes folgt eine Implementierung des wohlbekannten *Hallo, Welt!*-Programmes. Obwohl es sehr +einfach ist, eignet es sich sehr gut, Scalas Funktionsweise zu demonstrieren, ohne dass man viel +über die Sprache wissen muss. + + object HalloWelt { + def main(args: Array[String]) { + println("Hallo, Welt!") + } + } + +Die Struktur des Programmes sollte Java Anwendern bekannt vorkommen: es besteht aus einer Methode +namens `main`, welche die Kommandozeilenparameter als Feld (Array) von Zeichenketten (String) +übergeben bekommt. Der Körper dieser Methode besteht aus einem einzelnen Aufruf der vordefinierten +Methode `println`, die die freundliche Begrüßung als Parameter übergeben bekommt. Weiterhin hat die +`main`-Methode keinen Rückgabewert - sie ist also eine Prozedur. Daher ist es auch nicht notwendig, +einen Rückgabetyp zu spezifizieren. + +Was Java-Programmierern allerdings weniger bekannt sein sollte, ist die Deklaration `object +HalloWelt`, welche die Methode `main` enthält. Eine solche Deklaration stellt dar, was gemeinhin als +*Singleton Objekt* bekannt ist: eine Klasse mit nur einer Instanz. Im Beispiel oben werden also mit +dem Schlüsselwort `object` sowohl eine Klasse namens `HalloWelt` als auch die dazugehörige, +gleichnamige Instanz definiert. Diese Instanz wird erst bei ihrer erstmaligen Verwendung erstellt. + +Dem aufmerksamen Leser ist vielleicht aufgefallen, dass die `main`-Methode nicht als `static` +deklariert wurde. Der Grund dafür ist, dass statische Mitglieder (Attribute oder Methoden) in Scala +nicht existieren. Die Mitglieder von Singleton Objekten stellen in Scala dar, was Java und andere +Sprachen mit statischen Mitgliedern erreichen. + +### Das Beispiel kompilieren + +Um das obige Beispiel zu kompilieren, wird `scalac`, der Scala-Compiler verwendet. `scalac` arbeitet +wie die meisten anderen Compiler auch: er akzeptiert Quellcode-Dateien als Parameter, einige weitere +Optionen, und übersetzt den Quellcode in Java-Bytecode. Dieser Bytecode wird in ein oder mehrere +Java-konforme Klassen-Dateien, Dateien mit der Endung `.class`, geschrieben. + +Schreibt man den obigen Quellcode in eine Datei namens `HalloWelt.scala`, kann man diese mit dem +folgenden Befehl kompilieren (das größer-als-Zeichen `>` repräsentiert die Eingabeaufforderung und +sollte nicht mit geschrieben werden): + + > scalac HalloWelt.scala + +Damit werden einige Klassen-Dateien in das aktuelle Verzeichnis geschrieben. Eine davon heißt +`HalloWelt.class` und enthält die Klasse, die direkt mit dem Befehl `scala` ausgeführt werden kann, +was im folgenden Abschnitt erklärt wird. + +### Das Beispiel ausführen + +Sobald kompiliert, kann ein Scala-Programm mit dem Befehl `scala` ausgeführt werden. Die Anwendung +ist dem Befehl `java`, mit dem man Java-Programme ausführt, nachempfunden und akzeptiert dieselben +Optionen. Das obige Beispiel kann demnach mit folgendem Befehl ausgeführt werden, was das erwartete +Resultat ausgibt: + + > scala -classpath . HalloWelt + Hallo, Welt! + +## Interaktion mit Java + +Eine Stärke der Sprache Scala ist, dass man mit ihr sehr leicht mit Java interagieren kann. Alle +Klassen des Paketes `java.lang` stehen beispielsweise automatisch zur Verfügung, während andere +explizit importiert werden müssen. + +Als nächstes folgt ein Beispiel, was diese Interoperabilität demonstriert. Ziel ist es, das aktuelle +Datum zu erhalten und gemäß den Konventionen eines gewissen Landes zu formatieren, zum Beispiel +Frankreich. + +Javas Klassen-Bibliothek enthält viele nützliche Klassen, beispielsweise `Date` und `DateFormat`. +Dank Scala Fähigkeit, nahtlos mit Java zu interoperieren, besteht keine Notwendigkeit, äquivalente +Klassen in der Scala Klassen-Bibliothek zu implementieren - man kann einfach die entsprechenden +Klassen der Java-Pakete importieren: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +Scala Import-Anweisung ähnelt sehr der von Java, obwohl sie viel mächtiger ist. Mehrere Klassen des +gleichen Paketes können gleichzeitig importiert werden, indem sie, wie in der ersten Zeile, in +geschweifte Klammern geschrieben werden. Ein weiterer Unterschied ist, dass, wenn man alle +Mitglieder eines Paketes importieren will, einen Unterstrich (`_`) anstelle des Asterisk (`*`) +verwendet. Der Grund dafür ist, dass der Asterisk ein gültiger Bezeichner in Scala ist, +beispielsweise als Name für Methoden, wie später gezeigt wird. Die Import-Anweisung der dritten +Zeile importiert demnach alle Mitglieder der Klasse `DateFormat`, inklusive der statischen Methode +`getDateInstance` und des statischen Feldes `LONG`. + +Innerhalb der `main`-Methode wird zuerst eine Instanz der Java-Klasse `Date` erzeugt, welche +standardmäßig das aktuelle Datum enthält. Als nächstes wird mithilfe der statischen Methode +`getDateInstance` eine Instanz der Klasse `DateFormat` erstellt. Schließlich wird das aktuelle Datum +gemäß der Regeln der lokalisierten `DateFormat`-Instanz formatiert ausgegeben. Außerdem +veranschaulicht die letzte Zeile eine interessante Fähigkeit Scalas Syntax: Methoden, die nur einen +Parameter haben, können in der Infix-Syntax notiert werden. Dies bedeutet, dass der Ausdruck + + df format now + +eine andere, weniger verbose Variante des folgenden Ausdruckes ist: + + df.format(now) + +Dies scheint nur ein nebensächlicher, syntaktischer Zucker zu sein, hat jedoch bedeutende +Konsequenzen, wie im folgenden Abschnitt gezeigt wird. + +Um diesen Abschnitt abzuschließen, soll bemerkt sein, dass es außerdem direkt in Scala möglich ist, +von Java-Klassen zu erben sowie Java-Schnittstellen zu implementieren. + +## Alles ist ein Objekt + +Scala ist eine pur Objekt-orientierte Sprache, in dem Sinne dass *alles* ein Objekt ist, Zahlen und +Funktionen eingeschlossen. Der Unterschied zu Java ist, dass Java zwischen primitiven Typen, wie +`boolean` und `int`, und den Referenz-Typen unterscheidet und es nicht erlaubt ist, Funktionen wie +Werte zu behandeln. + +### Zahlen sind Objekte + +Zahlen sind Objekte und haben daher Methoden. Tatsächlich besteht ein arithmetischer Ausdruck wie +der folgende + + 1 + 2 * 3 / x + +exklusiv aus Methoden-Aufrufen, da es äquivalent zu folgendem Ausdruck ist, wie in vorhergehenden +Abschnitt gezeigt wurde: + + (1).+(((2).*(3))./(x)) + +Dies bedeutet außerdem, dass `+`, `*`, etc. in Scala gültige Bezeichner sind. + +Die Zahlen umschließenden Klammern der zweiten Variante sind notwendig, weil Scalas lexikalischer +Scanner eine Regel zur längsten Übereinstimmung der Token verwendet. Daher würde der folgende +Ausdruck: + + 1.+(2) + +in die Token `1.`, `+`, und `2` zerlegt werden. Der Grund für diese Zerlegung ist, dass `1.` eine +längere, gültige Übereinstimmung ist, als `1`. Daher würde das Token `1.` als das Literal `1.0` +interpretiert, also als Gleitkommazahl anstatt als Ganzzahl. Den Ausdruck als + + (1).+(2) + +zu schreiben, verhindert also, dass `1.` als Gleitkommazahl interpretiert wird. + +### Funktionen sind Objekte + +Vermutlich überraschender für Java-Programmierer ist, dass auch Funktionen in Scala Objekte sind. +Daher ist es auch möglich, Funktionen als Parameter zu übergeben, als Werte zu speichern, und von +anderen Funktionen zurückgeben zu lassen. Diese Fähigkeit, Funktionen wie Werte zu behandeln, ist +einer der Grundsteine eines sehr interessanten Programmier-Paradigmas, der *funktionalen +Programmierung*. + +Ein sehr einfaches Beispiel, warum es nützlich sein kann, Funktionen wie Werte zu behandeln, ist +eine Timer-Funktion, deren Ziel es ist, eine gewisse Aktion pro Sekunde durchzuführen. Wie übergibt +man die durchzuführende Aktion? Offensichtlich als Funktion. Diese einfache Art der Übergabe einer +Funktion sollte den meisten Programmieren bekannt vorkommen: dieses Prinzip wird häufig bei +Schnittstellen für Rückruf-Funktionen (call-back) verwendet, die ausgeführt werden, wenn ein +bestimmtes Ereignis eintritt. + +Im folgenden Programm akzeptiert die Timer-Funktion `oncePerSecond` eine Rückruf-Funktion als +Parameter. Deren Typ wird `() => Unit` geschrieben und ist der Typ aller Funktionen, die keine +Parameter haben und nichts zurück geben (der Typ `Unit` ist das Äquivalent zu `void`). Die +`main`-Methode des Programmes ruft die Timer-Funktion mit der Rückruf-Funktion auf, die einen Satz +ausgibt. In anderen Worten: das Programm gibt endlos den Satz "Die Zeit vergeht wie im Flug." +einmal pro Sekunde aus. + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { + callback() + Thread sleep 1000 + } + } + + def timeFlies() { + println("Die Zeit vergeht wie im Flug.") + } + + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +Weiterhin ist zu bemerken, dass, um die Zeichenkette auszugeben, die in Scala vordefinierte Methode +`println` statt der äquivalenten Methode in `System.out` verwendet wird. + +#### Anonyme Funktionen + +Während das obige Programm schon leicht zu verstehen ist, kann es noch verbessert werden. Als erstes +sei zu bemerken, dass die Funktion `timeFlies` nur definiert wurde, um der Funktion `oncePerSecond` +als Parameter übergeben zu werden. Dieser nur einmal verwendeten Funktion einen Namen zu geben, +scheint unnötig und es wäre angenehmer, sie direkt mit der Übergabe zu erstellen. Dies ist in Scala +mit *anonymen Funktionen* möglich, die eine Funktion ohne Namen darstellen. Die überarbeitete +Variante des obigen Timer-Programmes verwendet eine anonyme Funktion anstatt der Funktion +`timeFlies`: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { + callback() + Thread sleep 1000 + } + } + + def main(args: Array[String]) { + oncePerSecond(() => println("Die Zeit vergeht wie im Flug.")) + } + } + +Die anonyme Funktion erkennt man an dem Rechtspfeil `=>`, der die Parameter der Funktion von deren +Körper trennt. In diesem Beispiel ist die Liste der Parameter leer, wie man an den leeren Klammern +erkennen kann. Der Körper der Funktion ist derselbe, wie bei der `timeFlies` Funktion des +vorangegangenen Beispiels. + +## Klassen + +Wie weiter oben zu sehen war, ist Scala eine pur Objekt-orientierte Sprache, und als solche enthält +sie das Konzept von Klassen (der Vollständigkeit halber soll bemerkt sein, dass nicht alle +Objekt-orientierte Sprachen das Konzept von Klassen unterstützen, aber Scala ist keine von denen). +Klassen in Scala werden mit einer ähnlichen Syntax wie Java deklariert. Ein wichtiger Unterschied +ist jedoch, dass Scalas Klassen Argumente haben. Dies soll mit der folgenden Definition von +komplexen Zahlen veranschaulicht werden: + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +Diese Klasse akzeptiert zwei Argumente, den realen und den imaginären Teil der komplexen Zahl. Sie +müssen beim Erzeugen einer Instanz der Klasse übergeben werden: + + val c = new Complex(1.5, 2.3) + +Weiterhin enthält die Klasse zwei Methoden, `re` und `im`, welche als Zugriffsfunktionen (Getter) +dienen. Außerdem soll bemerkt sein, dass der Rückgabe-Typ dieser Methoden nicht explizit deklariert +ist. Der Compiler schlussfolgert ihn automatisch, indem er ihn aus dem rechten Teil der Methoden +ableitet, dass der Rückgabewert vom Typ `Double` ist. + +Der Compiler ist nicht immer fähig, auf den Rückgabe-Typ zu schließen, und es gibt leider keine +einfache Regel, vorauszusagen, ob er dazu fähig ist oder nicht. In der Praxis stellt das +üblicherweise kein Problem dar, da der Compiler sich beschwert, wenn es ihm nicht möglich ist. +Scala-Anfänger sollten versuchen, Typ-Deklarationen, die leicht vom Kontext abzuleiten sind, +wegzulassen, um zu sehen, ob der Compiler zustimmt. Nach einer gewissen Zeit, bekommt man ein Gefühl +dafür, wann man auf diese Deklarationen verzichten kann und wann man sie explizit angeben sollte. + +### Methoden ohne Argumente + +Ein Problem der obigen Methoden `re` und `im` ist, dass man, um sie zu verwenden, ein leeres +Klammerpaar hinter ihren Namen anhängen muss: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +Besser wäre es jedoch, wenn man den realen und imaginären Teil so abrufen könnte, als wären sie +Felder, also ohne das leere Klammerpaar. Mit Scala ist dies möglich, indem Methoden *ohne Argumente* +definiert werden. Solche Methoden haben keine Klammern nach ihrem Namen, weder bei ihrer Definition +noch bei ihrer Verwendung. Die Klasse für komplexe Zahlen kann demnach folgendermaßen umgeschrieben +werden: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + +### Vererbung und Überschreibung + +Alle Klassen in Scala erben von einer Oberklasse. Wird keine Oberklasse angegeben, wie bei der +Klasse `Complex` des vorhergehenden Abschnittes, wird implizit `scala.AnyRef` verwendet. + +Außerdem ist es möglich, von einer Oberklasse vererbte Methoden zu überschreiben. Dabei muss jedoch +explizit das Schlüsselwort `override` angegeben werden, um versehentliche Überschreibungen zu +vermeiden. Als Beispiel soll eine Erweiterung der Klasse `Complex` dienen, die die Methode +`toString` neu definiert: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + +## Container-Klassen und Musterabgleiche + +Eine Datenstruktur, die häufig in Programmen vorkommt, ist der Baum. Beispielsweise repräsentieren +Interpreter und Compiler Programme intern häufig als Bäume, XML-Dokumente sind Bäume und einige +Container basieren auf Bäumen, wie Rot-Schwarz-Bäume. + +Als nächstes wird anhand eines kleinen Programmes für Berechnungen gezeigt, wie solche Bäume in +Scala repräsentiert und manipuliert werden können. Das Ziel dieses Programmes ist, einfache +arithmetische Ausdrücke zu manipulieren, die aus Summen, Ganzzahlen und Variablen bestehen. +Beispiele solcher Ausdrücke sind: `1+2` und `(x+x)+(7+y)`. + +Dafür muss zuerst eine Repräsentation für die Ausdrücke gewählt werden. Die natürlichste ist ein +Baum, dessen Knoten Operationen (Additionen) und dessen Blätter Werte (Konstanten und Variablen) +darstellen. + +In Java würde man solche Bäume am ehesten mithilfe einer abstrakten Oberklasse für den Baum und +konkreten Implementierungen für Knoten und Blätter repräsentieren. In einer funktionalen Sprache +würde man algebraische Datentypen mit dem gleichen Ziel verwenden. Scala unterstützt das Konzept +einer Container-Klasse (case class), die einen gewissen Mittelweg dazwischen darstellen. Der +folgenden Quellcode veranschaulicht deren Anwendung: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +Die Tatsache, dass die Klassen `Sum`, `Var` und `Const` als Container-Klassen deklariert sind, +bedeutet, dass sie sich in einigen Gesichtspunkten von normalen Klassen unterscheiden: + +- das Schlüsselwort `new` ist nicht mehr notwendig, um Instanzen dieser Klassen zu erzeugen (man + kann also `Const(5)` anstelle von `new Const(5)` schreiben) +- Zugriffsfunktionen werden automatisch anhand der Parameter des Konstruktors erstellt (man kann + den Wert `v` einer Instanz `c` der Klasse `Const` erhalten, indem man `c.v` schreibt) +- der Compiler fügt Container-Klassen automatisch Implementierungen der Methoden `equals` und + `hashCode` hinzu, die auf der *Struktur* der Klassen basieren, anstelle deren Identität +- außerdem wird eine `toString`-Methode bereitgestellt, die einen Wert in Form der Quelle + darstellt (der String-Wert des Baum-Ausdruckes `x+1` ist `Sum(Var(x),Const(1))`) +- Instanzen dieser Klassen können mithilfe von Musterabgleichen zerlegt werden, wie weiter unten + zu sehen ist + +Da jetzt bekannt ist, wie die Datenstruktur der arithmetischen Ausdrücke repräsentiert wird, können +jetzt Operationen definiert werden, um diese zu manipulieren. Der Beginn dessen soll eine Funktion +darstellen, die Ausdrücke in einer bestimmten *Umgebung* auswertet. Das Ziel einer Umgebung ist es, +Variablen Werte zuzuweisen. Beispielsweise wird der Ausdruck `x+1` in der Umgebung, die der Variable +`x` den Wert `5` zuweist, geschrieben als `{ x -> 5 }`, mit dem Resultat `6` ausgewertet. + +Demnach muss ein Weg gefunden werden, solche Umgebungen auszudrücken. Dabei könnte man sich für eine +assoziative Datenstruktur entscheiden, wie eine Hash-Tabelle, man könnte jedoch auch direkt eine +Funktion verwenden. Eine Umgebung ist nicht mehr als eine Funktion, die Werte mit Variablen +assoziiert. Die obige Umgebung `{ x -> 5 }` wird in Scala folgendermaßen notiert: + + { case "x" => 5 } + +Diese Schreibweise definiert eine Funktion, welche bei dem String `"x"` als Argument die Ganzzahl +`5` zurückgibt, und in anderen Fällen mit einer Ausnahme fehlschlägt. + +Vor dem Schreiben der Funktionen zum Auswerten ist es sinnvoll, für die Umgebungen einen eigenen Typ +zu definieren. Man könnte zwar immer `String => Int` verwenden, es wäre jedoch besser einen +dedizierten Namen dafür zu verwenden, der das Programmieren damit einfacher macht und die Lesbarkeit +erhöht. Dies wird in Scala mit der folgenden Schreibweise erreicht: + + type Environment = String => Int + +Von hier an wird `Environment` als Alias für den Typ von Funktionen von `String` nach `Int` +verwendet. + +Nun ist alles für die Definition der Funktion zur Auswertung vorbereitet. Konzeptionell ist die +Definition sehr einfach: der Wert der Summe zweier Ausdrücke ist die Summe der Werte der einzelnen +Ausdrücke, der Wert einer Variablen wird direkt der Umgebung entnommen und der Wert einer Konstante +ist die Konstante selbst. Dies in Scala auszudrücken, ist nicht viel schwieriger: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +Diese Funktion zum Auswerten von arithmetischen Ausdrücken nutzt einen *Musterabgleich* (pattern +matching) am Baumes `t`. Intuitiv sollte die Bedeutung der einzelnen Fälle klar sein: + +1. Als erstes wird überprüft, ob `t` eine Instanz der Klasse `Sum` ist. Falls dem so ist, wird der +linke Teilbaum der Variablen `l` und der rechte Teilbaum der Variablen `r` zugewiesen. Daraufhin +wird der Ausdruck auf der rechten Seite des Pfeiles ausgewertet, der die auf der linken Seite +gebundenen Variablen `l` und `r` verwendet. + +2. Sollte die erste Überprüfung fehlschlagen, also `t` ist keine `Sum`, wird der nächste Fall +abgehandelt und überprüft, ob `t` eine `Var` ist. Ist dies der Fall, wird analog zum ersten Fall der +Wert an `n` gebunden und der Ausdruck rechts vom Pfeil ausgewertet. + +3. Schlägt auch die zweite Überprüfung fehl, also `t` ist weder `Sum` noch `Val`, wird überprüft, +ob es eine Instanz des Typs `Const` ist. Analog wird bei einem Erfolg wie bei den beiden +vorangegangenen Fällen verfahren. + +4. Schließlich, sollten alle Überprüfungen fehlschlagen, wird eine Ausnahme ausgelöst, die +signalisiert, dass der Musterabgleich nicht erfolgreich war. Dies wird unweigerlich geschehen, +sollten neue Baum-Unterklassen erstellt werden. + +Die prinzipielle Idee eines Musterabgleiches ist, einen Wert anhand einer Reihe von Mustern +abzugleichen und, sobald ein Treffer erzielt wird, Werte zu extrahieren, mit denen darauf +weitergearbeitet werden kann. + +Erfahrene Objekt-orientierte Programmierer werden sich fragen, warum `eval` nicht als Methode der +Klasse `Tree` oder dessen Unterklassen definiert wurde. Dies wäre möglich, da Container-Klassen +Methoden definieren können, wie normale Klassen auch. Die Entscheidung, einen Musterabgleich oder +Methoden zu verwenden, ist Geschmackssache, hat jedoch wichtige Auswirkungen auf die +Erweiterbarkeit: + +- einerseits ist es mit Methoden einfach, neue Arten von Knoten als Unterklassen von `Tree` + hinzuzufügen, andererseits ist die Ergänzung einer neuen Operation zur Manipulation des Baumes + mühsam, da sie die Modifikation aller Unterklassen von `Tree` erfordert +- nutzt man einen Musterabgleich kehrt sich die Situation um: eine neue Art von Knoten erfordert + die Modifikation aller Funktionen die einen Musterabgleich am Baum vollführen, wogegen eine neue + Operation leicht hinzuzufügen ist, indem einfach eine unabhängige Funktion dafür definiert wird + +Einen weiteren Einblick in Musterabgleiche verschafft eine weitere Operation mit arithmetischen +Ausdrücken: partielle Ableitungen. Dafür gelten zur Zeit folgende Regeln: + +1. die Ableitung einer Summe ist die Summe der Ableitungen +2. die Ableitung einer Variablen ist eins, wenn sie die abzuleitende Variable ist, ansonsten `0` +3. die Ableitung einer Konstanten ist `0` + +Auch diese Regeln können fast wörtlich in Scala übersetzt werden: + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +Diese Funktion führt zwei neue, mit dem Musterabgleich zusammenhängende Konzepte ein. Der zweite, +sich auf eine Variable beziehende Fall hat eine *Sperre* (guard), einen Ausdruck, der dem +Schlüsselwort `if` folgt. Diese Sperre verhindert eine Übereinstimmung, wenn der Ausdruck falsch +ist. In diesem Fall wird sie genutzt, die Konstante `1` nur zurückzugeben, wenn die Variable die +abzuleitende ist. Die zweite Neuerung ist der *Platzhalter* `_`, der mit allem übereinstimmt, jedoch +ohne einen Namen dafür zu verwenden. + +Die volle Funktionalität von Musterabgleichen wurde mit diesen Beispielen nicht demonstriert, doch +soll dies fürs Erste genügen. Eine Vorführung der beiden Funktionen an realen Beispielen steht immer +noch aus. Zu diesem Zweck soll eine `main`-Methode dienen, die den Ausdruck `(x+x)+(7+y)` als +Beispiel verwendet: zuerst wird der Wert in der Umgebung `{ x -> 5, y -> 7 }` berechnet und darauf +die beiden partiellen Ableitungen gebildet: + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { + case "x" => 5 + case "y" => 7 + } + println("Ausdruck: " + exp) + println("Auswertung mit x=5, y=7: " + eval(exp, env)) + println("Ableitung von x:\n " + derive(exp, "x")) + println("Ableitung von y:\n " + derive(exp, "y")) + } + +Führt man das Programm aus, erhält man folgende Ausgabe: + + Ausdruck: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Auswertung mit x=5, y=7: 24 + Ableitung von x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Ableitung von y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +Beim Anblick dieser Ausgabe ist offensichtlich, dass man die Ergebnisse der Ableitungen noch +vereinfachen sollte. Eine solche Funktion zum Vereinfachen von Ausdrücken, die Musterabgleiche +nutzt, ist ein interessantes, aber gar nicht so einfaches Problem, was als Übung offen steht. + +## Traits + +Neben dem Vererben von Oberklassen ist es in Scala auch möglich von mehreren, sogenannten *Traits* +zu erben. Der beste Weg für einen Java-Programmierer einen Trait zu verstehen, ist sich eine +Schnittstelle vorzustellen, die Implementierungen enthält. Wenn in Scala eine Klasse von einem Trait +erbt, implementiert sie dessen Schnittstelle und erbt dessen Implementierungen. + +Um die Nützlichkeit von Traits zu demonstrieren, werden wir ein klassisches Beispiel implementieren: +Objekte mit einer natürlichen Ordnung oder Rangfolge. Es ist häufig hilfreich, Instanzen einer +Klasse untereinander vergleichen zu können, um sie beispielsweise sortieren zu können. In Java +müssen die Klassen solcher Objekte die Schnittstelle `Comparable` implementieren. In Scala kann dies +mit einer äquivalenten, aber besseren Variante von `Comparable` als Trait bewerkstelligt werden, die +im Folgenden `Ord` genannt wird. + +Wenn Objekte verglichen werden, sind sechs verschiedene Aussagen sinnvoll: kleiner, kleiner gleich, +gleich, ungleich, größer, und größer gleich. Allerdings ist es umständlich, immer alle sechs +Methoden dafür zu implementieren, vor allem in Anbetracht der Tatsache, dass vier dieser sechs durch +die verbliebenen zwei ausgedrückt werden können. Sind beispielsweise die Aussagen für gleich und +kleiner gegeben, kann man die anderen damit ausdrücken. In Scala können diese Beobachtungen mit +dem folgenden Trait zusammengefasst werden: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +Diese Definition erzeugt sowohl einen neuen Typ namens `Ord`, welcher dieselbe Rolle wie Javas +Schnittstelle `Comparable` spielt, und drei vorgegebenen Funktionen, die auf einer vierten, +abstrakten basieren. Die Methoden für Gleichheit und Ungleichheit erscheinen hier nicht, da sie +bereits in allen Objekten von Scala vorhanden sind. + +Der Typ `Any`, welcher oben verwendet wurde, stellt den Ober-Typ aller Typen in Scala dar. Er kann +als noch allgemeinere Version von Javas `Object` angesehen werden, da er außerdem Ober-Typ der +Basis-Typen wie `Int` und `Float` ist. + +Um Objekte einer Klasse vergleichen zu können, ist es also hinreichend, Gleichheit und die +kleiner-als-Beziehung zu implementieren, und dieses Verhalten gewissermaßen mit der eigentlichen +Klasse zu vermengen (mix in). Als Beispiel soll eine Klasse für Datumsangaben dienen, die Daten +eines gregorianischen Kalenders repräsentiert. Solche Daten bestehen aus Tag, Monat und Jahr, welche +durch Ganzzahlen dargestellt werden: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + + override def toString = year + "-" + month + "-" + day + +Der wichtige Teil dieser Definition ist die Deklaration `extends Ord`, welche dem Namen der Klasse +und deren Parametern folgt. Sie sagt aus, dass `Date` vom Trait `Ord` erbt. + +Nun folgt eine Re-Implementierung der Methode `equals`, die von `Object` geerbt wird, so dass die +Daten korrekt nach ihren Feldern verglichen werden. Die vorgegebene Implementierung von `equals` ist +dafür nicht nützlich, da in Java Objekte physisch, also nach deren Adressen im Speicher, verglichen +werden. Daher verwenden wir folgende Definition: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +Diese Methode verwendet die vordefinierten Methoden `isInstanceOf` und `asInstanceOf`. Erstere +entspricht Javas `instanceof`-Operator und gibt `true` zurück, wenn das zu testende Objekt eine +Instanz des angegebenen Typs ist. Letztere entspricht Javas Operator für Typ-Umwandlungen (cast): +ist das Objekt eine Instanz des angegebenen Typs, kann es als solcher angesehen und gehandhabt +werden, ansonsten wird eine `ClassCastException` ausgelöst. + +Schließlich kann die letzte Methode definiert werden, die für `Ord` notwendig ist, und die +kleiner-als-Beziehung implementiert. Diese nutzt eine andere, vordefinierte Methode, namens `error`, +des Paketes `sys`, welche eine `RuntimeException` mit der angegebenen Nachricht auslöst. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + sys.error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + } + +Diese Methode vervollständigt die Definition der `Date`-Klasse. Instanzen dieser Klasse stellen +sowohl Daten als auch vergleichbare Objekte dar. Vielmehr implementiert diese Klasse alle sechs +Methoden, die für das Vergleichen von Objekten notwendig sind: `equals` und `<`, die direkt in der +Definition von `Date` vorkommen, sowie die anderen, in dem Trait `Ord` definierten Methoden. + +Traits sind nützlich in Situationen wie der obigen, den vollen Funktionsumfang hier zu zeigen, würde +allerdings den Rahmen dieses Dokumentes sprengen. + +## Generische Programmierung + +Eine weitere Charakteristik Scalas, die in diesem Tutorial vorgestellt werden soll, behandelt das +Konzept der generischen Programmierung. Java-Programmierer, die die Sprache noch vor der Version 1.5 +kennen, sollten mit den Problemen vertraut sein, die auftreten, wenn generische Programmierung nicht +unterstützt wird. + +Generische Programmierung bedeutet, Quellcode nach Typen zu parametrisieren. Beispielsweise stellt +sich die Frage für einen Programmierer bei der Implementierung einer Bibliothek für verkettete +Listen, welcher Typ für die Elemente verwendet werden soll. Da diese Liste in verschiedenen +Zusammenhängen verwendet werden soll, ist es nicht möglich, einen spezifischen Typ, wie `Int`, zu +verwenden. Diese willkürliche Wahl wäre sehr einschränkend. + +Aufgrund dieser Probleme griff man in Java vor der Einführung der generischen Programmierung zu dem +Mittel, `Object`, den Ober-Typ aller Typen, als Element-Typ zu verwenden. Diese Lösung ist +allerdings auch weit entfernt von Eleganz, da sie sowohl ungeeignet für die Basis-Typen, wie `int` +oder `float`, ist, als auch viele explizite Typ-Umwandlungen für den nutzenden Programmierer +bedeutet. + +Scala ermöglicht es, generische Klassen und Methoden zu definieren, um diesen Problemen aus dem Weg +zu gehen. Für die Demonstration soll ein einfacher, generischer Container als Referenz-Typ dienen, +der leer sein kann, oder auf ein Objekt des generischen Typs zeigt: + + class Reference[T] { + private var contents: T = _ + + def get: T = contents + + def set(value: T) { + contents = value + } + } + +Die Klasse `Reference` ist anhand des Types `T` parametrisiert, der den Element-Typ repräsentiert. +Dieser Typ wird im Körper der Klasse genutzt, wie bei dem Feld `contents`. Dessen Argument wird +durch die Methode `get` abgefragt und mit der Methode `set` verändert. + +Der obige Quellcode führt veränderbare Variablen in Scala ein, welche keiner weiteren Erklärung +erfordern sollten. Schon interessanter ist der initiale Wert dieser Variablen, der mit `_` +gekennzeichnet wurde. Dieser Standardwert ist für numerische Typen `0`, `false` für Wahrheitswerte, +`()` für den Typ `Unit` und `null` für alle anderen Typen. + +Um diese Referenz-Klasse zu verwenden, muss der generische Typ bei der Erzeugung einer Instanz +angegeben werden. Für einen Ganzzahl-Container soll folgendes Beispiel dienen: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +Wie in dem Beispiel zu sehen ist, muss der Wert, der von der Methode `get` zurückgegeben wird, nicht +umgewandelt werden, wenn er als Ganzzahl verwendet werden soll. Es wäre außerdem nicht möglich, +einen Wert, der keine Ganzzahl ist, in einem solchen Container zu speichern, da er speziell und +ausschließlich für Ganzzahlen erzeugt worden ist. + +## Zusammenfassung + +Dieses Dokument hat einen kurzen Überblick über die Sprache Scala gegeben und dazu einige einfache +Beispiele verwendet. Interessierte Leser können beispielsweise mit dem Dokument *Scala by Example* +fortfahren, welches fortgeschrittenere Beispiele enthält, und die *Scala Language Specification* +konsultieren, sofern nötig. + diff --git a/es/overviews/parallel-collections/concrete-parallel-collections.md b/es/overviews/parallel-collections/concrete-parallel-collections.md index f14cdab316..fd1f7f15b1 100644 --- a/es/overviews/parallel-collections/concrete-parallel-collections.md +++ b/es/overviews/parallel-collections/concrete-parallel-collections.md @@ -1,6 +1,6 @@ --- layout: overview-large -title: Concrete Parallel Collection Classes +title: Clases Concretas de las Colecciones Paralelas disqus: true @@ -9,14 +9,9 @@ num: 2 language: es --- -## Parallel Array +## Array Paralelo -A [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) -sequence holds a linear, -contiguous array of elements. This means that the elements can be accessed and -updated efficiently by modifying the underlying array. Traversing the -elements is also very efficient for this reason. Parallel arrays are like -arrays in the sense that their size is constant. +Una secuencia [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) contiene un conjunto de elementos contiguos lineales. Esto significa que los elementos pueden ser accedidos y actualizados (modificados) eficientemente al modificar la estructura subyacente (un array). El iterar sobre sus elementos es también muy eficiente por esta misma razón. Los Arrays Paralelos son como arrays en el sentido de que su tamaño es constante. scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... @@ -27,32 +22,14 @@ arrays in the sense that their size is constant. scala> pa map (x => (x - 1) / 2) res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... -Internally, splitting a parallel array -[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -amounts to creating two new splitters with their iteration indices updated. -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -are slightly more involved.Since for most transformer methods (e.g. `flatMap`, `filter`, `takeWhile`, -etc.) we don't know the number of elements (and hence, the array size) in -advance, each combiner is essentially a variant of an array buffer with an -amortized constant time `+=` operation. Different processors add elements to -separate parallel array combiners, which are then combined by chaining their -internal arrays. The underlying array is only allocated and filled in parallel -after the total number of elements becomes known. For this reason, transformer -methods are slightly more expensive than accessor methods. Also, note that the -final array allocation proceeds sequentially on the JVM, so this can prove to -be a sequential bottleneck if the mapping operation itself is very cheap. - -By calling the `seq` method, parallel arrays are converted to `ArraySeq` -collections, which are their sequential counterparts. This conversion is -efficient, and the `ArraySeq` is backed by the same underlying array as the -parallel array it was obtained from. - - -## Parallel Vector - -A [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) -is an immutable sequence with a low-constant factor logarithmic access and -update time. +Internamente, para partir el array para que sea procesado de forma paralela se utilizan [splitters]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "partidores"). El Splitter parte y crea dos nuevos splitters con sus índices actualizados. A continuación son utilizados los [combiners]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "combinadores"), que necesitan un poco más de trabajo. Ya que en la mayoría de los métodos transformadores (ej: `flatMap`, `filter`, `takeWhile`, etc.) previamente no es sabido la cantidad de elementos (y por ende, el tamaño del array), cada combiner es esencialmente una variante de un array buffer con un tiempo constante de la operación `+=`. Diferentes procesadores añaden elementos a combiners de arrays separados, que después son combinados al encadenar sus arrays internos. El array subyacente se crea en memoria y se rellenan sus elementos después que el número total de elementos es conocido. Por esta razón, los métodos transformadores son un poco más caros que los métodos de acceso. También, nótese que la asignación de memoria final procede secuencialmente en la JVM, lo que representa un cuello de botella si la operación de mapeo (el método transformador aplicado) es en sí económico (en términos de procesamiento). + +Al invocar el método `seq`, los arrays paralelos son convertidos al tipo de colección `ArraySeq`, que vendría a ser la contraparte secuencial del `ParArray`. Esta conversión es eficiente, y el `ArraySeq` utiliza a bajo nivel el mismo array que había sido obtenido por el array paralelo. + + +## Vector Paralelo + +Un [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) es una secuencia inmutable con un tiempo de acceso y modificación logarítimico bajo a constante. scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... @@ -60,28 +37,13 @@ update time. scala> pv filter (_ % 2 == 0) res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... -Immutable vectors are represented by 32-way trees, so -[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s -are split byassigning subtrees to each splitter. -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -currently keep a vector of -elements and are combined by lazily copying the elements. For this reason, -transformer methods are less scalable than those of a parallel array. Once the -vector concatenation operation becomes available in a future Scala release, -combiners will be combined using concatenation and transformer methods will -become much more efficient. - -Parallel vector is a parallel counterpart of the sequential -[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), -so conversion between the two takes constant time. +Los vectores inmutables son representados por árboles, por lo que los splitters dividen pasándose subárboles entre ellos. Los combiners concurrentemente mantienen un vector de elementos y son combinados al copiar dichos elementos de forma "retardada". Es por esta razón que los métodos tranformadores son menos escalables que sus contrapartes en arrays paralelos. Una vez que las operaciones de concatenación de vectores estén disponibles en una versión futura de Scala, los combiners podrán usar dichas características y hacer más eficientes los métodos transformadores. +Un vector paralelo es la contraparte paralela de un [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) secuencial, por lo tanto la conversión entre estas dos estructuras se efectúa en tiempo constante. -## Parallel Range +## Rango (Range) Paralelo -A [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) -is an ordered sequence of elements equally spaced apart. A parallel range is -created in a similar way as the sequential -[Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html): +Un [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) es una secuencia ordenada separada por intervalos iguales (ej: 1, 2, 3 o 1, 3, 5, 7). Un rango paralelo es creado de forma similar al [Rango](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) secuencial: scala> 1 to 3 par res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) @@ -89,22 +51,15 @@ created in a similar way as the sequential scala> 15 to 5 by -2 par res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) -Just as sequential ranges have no builders, parallel ranges have no -[combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. -Mapping the elements of a parallel range produces a parallel vector. -Sequential ranges and parallel ranges can be converted efficiently one from -another using the `seq` and `par` methods. +Tal como los rangos secuenciales no tienen constructores, los rangos paralelos no tienen [combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. Mapear elementos de un rango paralelo produce un vector paralelo. Los rangos secuenciales y paralelos pueden ser convertidos de uno a otro utilizando los métodos `seq` y `par`. + scala> (1 to 5 par) map ((x) => x * 2) + res2: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(2, 4, 6, 8, 10) -## Parallel Hash Tables -Parallel hash tables store their elements in an underlying array and place -them in the position determined by the hash code of the respective element. -Parallel mutable hash sets -([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) -and parallel mutable hash maps -([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) -are based on hash tables. +## Tablas Hash Paralelas + +Las tablas hash paralelas almacenan sus elementos en un array subyacente y los almacenan en una posición determinada por el código hash del elemento respectivo. Las versiones mutables de los hash sets paralelos ([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) y los hash maps paraleos ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) están basados en tablas hash. scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... @@ -112,34 +67,14 @@ are based on hash tables. scala> phs map (x => x * x) res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... -Parallel hash table combiners sort elements into buckets according to their -hashcode prefix. They are combined by simply concatenating these buckets -together. Once the final hash table is to be constructed (i.e. combiner -`result` method is called), the underlying array is allocated and the elements -from different buckets are copied in parallel to different contiguous segments -of the hash table array. - -Sequential hash maps and hash sets can be converted to their parallel variants -using the `par` method. Parallel hash tables internally require a size map -which tracks the number of elements in different chunks of the hash table. -What this means is that the first time that a sequential hash table is -converted into a parallel hash table, the table is traversed and the size map -is created - for this reason, the first call to `par` takes time linear in the -size of the hash table. Further modifications to the hash table maintain the -state of the size map, so subsequent conversions using `par` and `seq` have -constant complexity. Maintenance of the size map can be turned on and off -using the `useSizeMap` method of the hash table. Importantly, modifications in -the sequential hash table are visible in the parallel hash table, and vice -versa. - - -## Parallel Hash Tries - -Parallel hash tries are a parallel counterpart of the immutable hash tries, -which are used to represent immutable sets and maps efficiently. They are -supported by classes -[immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) -and +Los combiners de las tablas hash ordenan los elementos en posiciones de acuerdo a su código hash. Se combinan simplemente concatenando las estructuras que contienen dichos elementos. Una vez que la tabla hash final es construida (es decir, se invoca el método `result` del combiner), el array subyacente es rellenado y los elementos de las diferentes estructuras previas son copiados en paralelo a diferentes segmentos continuos del array de la tabla hash. + +Los "Mapas Hash" (Hash Maps) y los "Conjuntos Hash" (Hash Sets) secuenciales pueden ser convertidos a sus variantes paralelas usando el método `par`. Las tablas hash paralelas internamente necesitan de un mapa de tamaño que mantiene un registro del número de elementos en cada pedazo de la hash table. Lo que esto significa es que la primera vez que una tabla hash secuencial es convertida a una tabla hash paralela, la tabla es recorrida y el mapa de tamaño es creado - es por esta razón que la primera llamada a `par` requiere un tiempo lineal con respecto al tamaño total de la tabla. Cualquier otra modificación que le siga mantienen el estado del mapa de tamaño, por lo que conversiones sucesivas entre `par` y `seq` tienen una complejidad constante. El mantenimiento del tamaño del mapa puede ser habilitado y deshabilitado utilizando el método `useSizeMap` de la tabla hash. Es importante notar que las modificaciones en la tabla hash secuencial son visibles en la tabla hash paralela, y viceversa. + +## Hash Tries Paralelos + +Los Hash Tries paralelos son la contraparte paralela de los hash tries inmutables, que son usados para representar conjuntos y mapas inmutables de forma eficiente. Las clases involucradas son: [immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) +y [immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) @@ -148,27 +83,14 @@ and scala> phs map { x => x * x } sum res0: Int = 332833500 -Similar to parallel hash tables, parallel hash trie -[combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -pre-sort the -elements into buckets and construct the resulting hash trie in parallel by -assigning different buckets to different processors, which construct the -subtries independently. +De forma similar a las tablas hash paralelas, los [combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) de los hash tries paralelos pre-ordenan los elementos en posiciones y construyen el hash trie resultante en paralelo al asignarle distintos grupos de posiciones a diferentes procesadores, los cuales contruyen los sub-tries independientemente. -Parallel hash tries can be converted back and forth to sequential hash tries -by using the `seq` and `par` method in constant time. +Los hash tries paralelos pueden ser convertidos hacia y desde hash tries secuenciales por medio de los métodos `seq` y `par`. -## Parallel Concurrent Tries +## Tries Paralelos Concurrentes -A [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) -is a concurrent thread-safe map, whereas a -[mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) -is its parallel counterpart. While most concurrent data structures do not guarantee -consistent traversal if the the data structure is modified during traversal, -Ctries guarantee that updates are only visible in the next iteration. This -means that you can mutate the concurrent trie while traversing it, like in the -following example which outputs square roots of number from 1 to 99: +Un [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) es un mapa thread-safe (seguro ante la utilización de múltiples hilos concurrentes) mientras que [mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) es su contraparte paralela. Si bien la mayoría de las estructuras de datos no garantizan una iteración consistente si la estructura es modificada en medio de dicha iteración, los tries concurrentes garantizan que las actualizaciones sean solamente visibles en la próxima iteración. Esto significa que es posible mutar el trie concurrente mientras se está iterando sobre este, como en el siguiente ejemplo, que computa e imprime las raíces cuadradas de los números entre 1 y 99: scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... @@ -190,20 +112,14 @@ following example which outputs square roots of number from 1 to 99: ... -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -are implemented as `TrieMap`s under the hood-- since this is a -concurrent data structure, only one combiner is constructed for the entire -transformer method invocation and shared by all the processors. +Para ofrecer más detalles de lo que sucede bajo la superficie, los [Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) son implementados como `TrieMap`s --ya que esta es una estructura de datos concurrente, solo un combiner es construido para todo la invocación al método transformador y compartido por todos los procesadores. -As with all parallel mutable collections, `TrieMap`s and parallel `ParTrieMap`s obtained -by calling `seq` or `par` methods are backed by the same store, so -modifications in one are visible in the other. Conversions happen in constant -time. +Al igual que todas las colecciones paralelas mutables, `TrieMap`s y la versión paralela, `ParTrieMap`s obtenidas mediante los métodos `seq` o `par` subyacentemente comparten la misma estructura de almacenamiento, por lo tanto modificaciones en una es visible en la otra. -## Performance characteristics +## Características de desmpeño (performance) -Performance characteristics of sequence types: +Performance de los tipo secuencia: | | head | tail | apply | update| prepend | append | insert | | -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | @@ -211,50 +127,49 @@ Performance characteristics of sequence types: | `ParVector` | eC | eC | eC | eC | eC | eC | - | | `ParRange` | C | C | C | - | - | - | - | -Performance characteristics of set and map types: +Performance para los sets y maps: | | lookup | add | remove | | -------- | ---- | ---- | ---- | -| **immutable** | | | | +| **inmutables** | | | | | `ParHashSet`/`ParHashMap`| eC | eC | eC | -| **mutable** | | | | +| **mutables** | | | | | `ParHashSet`/`ParHashMap`| C | C | C | | `ParTrieMap` | eC | eC | eC | -### Key - -The entries in the above two tables are explained as follows: +Los valores en las tablas de arriba se explican de la siguiente manera: | | | | --- | ---- | -| **C** | The operation takes (fast) constant time. | -| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| -| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | -| **Log** | The operation takes time proportional to the logarithm of the collection size. | -| **L** | The operation is linear, that is it takes time proportional to the collection size. | -| **-** | The operation is not supported. | +| **C** | La operación toma un tiempo constante (rápido) | +| **eC** | La opración toma tiempo efectivamente constante, pero esto puede depender de algunas suposiciones como el tamaño máximo de un vector o la distribución de las claves hash. | +| **aC** | La operación requiere un tiempo constante amortizado. Algunas invocaciones de la operación pueden tomar un poco más de tiempo, pero si en promedio muchas operaciones son realizadas solo es requerido tiempo constante. | +| **Log** | La operación requiere de un tiempo proporcional al logaritmo del tamaño de la colección. | +| **L** | La operación es linea, es decir que requiere tiempo proporcional al tamaño de la colección. | +| **-** | La operación no es soportada. | -The first table treats sequence types--both immutable and mutable--with the following operations: +La primer tabla considera tipos secuencia --ambos mutables e inmutables-- con las siguientes operaciones: | | | | --- | ---- | -| **head** | Selecting the first element of the sequence. | -| **tail** | Producing a new sequence that consists of all elements except the first one. | -| **apply** | Indexing. | -| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences. | -| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | +| **head** | Seleccionar el primer elemento de la secuencia. | +| **tail** | Produce una nueva secuencia que contiene todos los elementos menos el primero. | +| **apply** | Indexación. Seleccionar un elemento por posición. | +| **update** | Actualización funcional (con `updated`) para secuencias inmutables, actualización real con efectos laterales para secuencias mutables. | +| **prepend**| Añadir un elemento al principio de la colección. Para secuencias inmutables, esto produce una nueva secuencia. Para secuencias mutables, modifica la secuencia existente. | +| **append** | Añadir un elemento al final de la secuencia. Para secuencias inmutables, produce una nueva secuencia. Para secuencias mutables modifica la secuencia existente. | +| **insert** | Inserta un elemento a una posición arbitraria. Solamente es posible en secuencias mutables. | -The second table treats mutable and immutable sets and maps with the following operations: +La segunda tabla trata sets y maps, tanto mutables como inmutables, con las siguientes operaciones: | | | | --- | ---- | -| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | -| **add** | Adding a new element to a set or key/value pair to a map. | +| **lookup** | Comprueba si un elemento es contenido en un set, o selecciona un valor asociado con una clave en un map. | +| **add** | Añade un nuevo elemento a un set o un par clave/valor a un map. | | **remove** | Removing an element from a set or a key from a map. | -| **min** | The smallest element of the set, or the smallest key of a map. | +| **remove** | Elimina un elemento de un set o una clave de un map. | +| **min** | El menor elemento de un set o la menor clave de un mapa. | diff --git a/resources/images/language/de.png b/resources/images/language/de.png new file mode 100755 index 0000000000..e840992d97 Binary files /dev/null and b/resources/images/language/de.png differ diff --git a/tutorials/scala-for-java-programmers.md b/tutorials/scala-for-java-programmers.md index 523612a8fd..1e57e6d02c 100644 --- a/tutorials/scala-for-java-programmers.md +++ b/tutorials/scala-for-java-programmers.md @@ -5,7 +5,7 @@ overview: scala-for-java-programmers disqus: true multilingual-overview: true -languages: [es, ko] +languages: [es, ko, de] --- By Michel Schinz and Philipp Haller