martedì 23 agosto 2011

Introduzione alla Scala per sviluppatori Java


Scala unisce i paradigmi di programmazione object-oriented e funzionale, utilizzando una sintassi concisa che è pienamente compatibile con Java e gira su JVM. Il supporto per lo stile di programmazione funzionale, e in particolare le espressioni lambda, che non sono ora attesi da aggiungere a Java fino JavaSE 8 nel 2012, può aiutare a ridurre la quantità di codice refusi che si sono tenuti a scrivere, forse rendendo più facile mettere a fuoco sul compito in mano. Questo articolo fornisce un'introduzione a Scala.

scala> val columbus: Int = 1492 
Per iniziare, basta installare l'ultima distribuzione Scala del T pila ypesafe , aprire un prompt dei comandi e digitare 'scala': questo avrà inizio il REPL (read-eval ciclo di stampa) ambiente interattivo di programmazione. Ora siete pronti ad entrare la prima linea Scala:
columbus: Int = 1492
Abbiamo appena dichiarato una variabile di tipo int con valore 1492, proprio come faremmo in Java con "Int columbus = 1492;". La differenza a parte la sintassi inverso di mettere il tipo, dopo la variabile a Scala è che il "val" dichiara esplicitamente la variabile come immutabile. Se proviamo a modificarlo:
scala> columbus = 1500 
<console>: 8: errore: riassegnazione a val 
  columbus = 1500 
         ^
Notate come il messaggio indica precisamente dove l'errore sta nella linea. Provare a dichiarare la variabile di nuovo, ma questa volta come "var" per renderlo mutevole. A proposito, il compilatore è abbastanza intelligente per sapere che 1492 è un numero intero, e quindi non è necessario specificare il tipo a tutti:
scala> var = 1492 columbus 
columbus: Int = 1492
scala> columbus = 1500 
columbus: Int = 1500
Passando, definiamo una classe:
scala> classe Impiegato caso (name: String = "ospite", età: Int = 30, società: String = 
"DevCode") 
classe Employee definito 
Abbiamo definito un Dipendente classe con 3 campi immutabile chiamato nome, età e società con i valori di default. La parola "caso" è analoga alla istruzione switch in Java, ma più flessibile.Ciò significa che la classe ha un meccanismo aggiuntivo per il pattern matching, così come le altre cose, tra i quali un metodo factory per creare istanze (non è necessario utilizzare la parola chiave "nuovo" per creare uno). Allo stesso modo non c'è bisogno di creare getter default. A differenza di Java le variabili sono pubbliche per impostazione predefinita (non protetto) e Scala crea un getter per le variabili pubblica denominata dopo il nome della variabile stessa. Si potrebbe fare campi mutevole e / o private se si voleva utilizzando "var" di fronte a dei parametri (ad esempio caso Persona di classe (private var name: String)).
Creiamo alcuni casi in modi diversi per esporre le varie caratteristiche, come il nome e gli argomenti di default (disponibile a partire Scala 2.8):
scala> Val Guest dipendenti = () 
della clientela: Impiegato Impiegato = (guest, 30, DevCode)
scala> val guestAge = guest.age / / (il getter di default per la variabile età) 
guestAge: Int = 300
scala> val anna dipendenti = ("Anna") 
anna: Impiegato = dipendenti (Anna, 30, DevCode)
scala> val thomas dipendenti = ("Tommaso", 41) 
thomas: Impiegato Impiegato = (Thomas, 41, DevCode)
scala> val luke = dipendenti ("Luca", azienda = "LucasArt") 
luke: Impiegato Impiegato = (Luca, 30, LucasArt)
scala> val yoda = luke.copy ("Yoda", età = 800) 
Yoda: Impiegato = dipendenti (Yoda, 800, LucasArt)
Tuttavia, i seguenti
scala> val darth dipendenti = ("Darth", "DevCode") 
<console>: 9: errore: tipo non corrispondente; 
trovato: java.lang.String ("DevCode") 
richiesto: Int. 
è verificato un errore in un'applicazione che coinvolgono gli argomenti di default. 
    val darth = dipendenti ("Darth", "DevCode") 
                     ^
... non funziona (non perché Darth non è impiegato presso DevCode!) ma perché il costruttore si aspetta il parametro dell'età in questa posizione in quanto l'argomento non viene nominato esplicitamente.
Ora ci sposteremo a collezioni, poiché è qui che le cose stanno diventando davvero emozionante.
Con Generics (Java 5 in poi), Java possono ad esempio iterare su una lista di interi, scrivendo quanto segue:
Lista numeri <integer> = new <integer> ArrayList (); 
numbers.add (1); 
numbers.add (2); 
numbers.add (3); 
per (Integer n: i numeri) { 
System.out.println (" numero "+ n); 
}
che producono
Numero 1
Numero 2
Numero 3
Collezioni Scala sistematicamente distinguere tra immutabile e mutevole collezioni, ma incoraggiare immutabilità con la costruzione di collezioni immutabile di default. Essi simulano integrazioni, aggiornamenti o rimozioni ritornando nuove collezioni da tali operazioni, invece di modificarli.
L'equivalente del codice Scala al codice precedente Java può essere scritto:
scala> Numeri val = Lista (1,2,3) 
numeri: Lista [Int.] = Lista (1, 2, 3)
scala> per (n <- numeri) println ("Number" + n) 
Numero 1 
Numero 2 
Numero 3
Questo costrutto ciclo "for" è molto vicino allo stile imperativo di programmazione Java. Un altro modo per scrivere a Scala (e molte lingue sulla JVM come Groovy, JRuby o Jython) comporta uno stile più funzionale, usando le espressioni lambda (a volte indicato come chiusure). In breve lambda sono solo le funzioni che è possibile passare in giro come parametri. Queste funzioni prendono come parametri di input (nel nostro caso la "n" numero intero) e ritorno come il loro risultato l'ultima affermazione del loro corpo. Sono in forma
functionName {ingresso = corpo>}
scala> numbers.foreach {n: Int => / / basta premere Invio per proseguire sulla riga successiva 
   | println ("Number" + n) 
   |} 
Numero 1 
Numero 2 
Numero 3
In tal caso il corpo ha una sola istruzione (println. ..) e restituisce quindi Unità cioè un "risultato vuoto" o meno equivalente a vuoto in Java ad eccezione di "vuoto" non restituisce nulla.
Invece di limitarsi a stampare la nostra lista di numeri, diciamo che vogliamo manipolare e trasformare gli elementi, in questo caso vogliamo richiamare i metodi che produrrà un elenco risulta che si può riutilizzare in seguito. Proviamo con alcuni esempi:
scala> val reversedList = numbers.reverse 
reversedList: Lista [Int.] = Lista (3, 2, 1)
scala> val numbersLessThan3 numbers.filter = {n => n <3} 
numbersLessThan3: Lista [Int.] = Lista (1, 2)
scala> val oddNumbers numbers.filterNot = {n => n% 2 == 0} 
oddNumbers: Lista [Int.] = Lista (1, 3)
scala> val higherNumbers numbers.map = {n => n + 10} 
higherNumbers: Lista [Int.] = List (11, 12, 13)
Questa ultima trasformazione "mappa" è molto utile, si applica la chiusura ad ogni elemento della lista e il suo risultato è una lista delle stesse dimensioni che contiene ogni elemento trasformato.
Un ultimo metodo che vorremmo introdurre qui è il metodo "foldLeft", che si propaga stato da un elemento all'altro. Ad esempio, per somma elementi di una lista è necessario accumulare loro e tenere traccia del contatore intermedio da un elemento all'altro:
scala> val sumOfNumbers = numbers.foldLeft (0) {(totale, elemento) => 
   | totale + elemento 
   |} 
sumOfNumbers: Int = 6
Il valore 0 dato come primo argomento a foldLeft è il valore iniziale (il che significa totale = 0 quando si applica la funzione per l'elemento primo elenco). La notazione (totale, elemento) rappresenta una Tuple2, che è a Scala una tupla di 2 elementi (ad esempio, per rappresentare uno spazio 3D coordinate spesso è utile fare riferimento a una Tuple3 (x, y, z), ecc ..). Si noti che per somma l'API Scala fornisce in realtà un metodo di "somma", così l'ultima affermazione avrebbe potuto essere scritto:
scala> val sumOfNumbers = numbers.sum 
sumOfNumbers: Int = 6
Ci sono più molti di questi metodi di trasformazione di raccolta che è possibile verificare dalla API scaladoc . È anche possibile catena di questi metodi (ad esempio numbers.reverse.filter ...) per ottenere codice più conciso, anche se può influenzare la leggibilità.
Infine, una notazione più breve equivalente a {n => n + 10} esiste nella forma di (_ + 10), il che significa che non si deve dichiarare il parametro di ingresso se è solo implicito il metodo che si sta invocando; nel nostro caso "n" è chiamata una variabile anonimo perché si può chiamare qualcosa che ti piace come "x" o "numero", in modo da sottolineare significa un vuoto hai bisogno di riempire con ogni elemento della vostra collezione. (Groovy si riserva la parola "si" al posto di _, e Python usa "sé").
scala> val higherNumbers = numbers.map (_ +10) 
higherNumbers: Lista [Int.] = List (11, 12, 13)
Dopo le manipolazioni di base su numeri interi siamo pronti a saltare in collezione trasformazioni che coinvolgono più oggetti complessi, per esempio utilizzando la classe dei dipendenti che abbiamo definito in precedenza:
scala> val allEmployees = Lista (Luca, anna, ospite, Yoda, thomas) 
allEmployees: Lista [dipendenti] = Lista (Employee (Luca, 30, LucasArt), 
dipendenti (Anna, 30, DevCode), dipendenti (guest, 30, DevCode), 
dipendenti (Yoda, 800, LucasArt), dipendenti (Thomas, 41, DevCode))
Da questo elenco di 5 elementi possiamo mantenere, ad esempio, i dipendenti DevCode applicando un filtro che non mancherà di tenere i dipendenti per i quali la funzione anonima restituisce True:
scala> val devcodeEmployees = {allEmployees.filter _.company == "DevCode"} 
devcodeEmployees: Lista [Employee] = Lista (Employee (Anna, 30, DevCode), 
dipendenti (guest, 30, DevCode), dipendenti (Thomas, 41 , DevCode))
. scala> val oldEmployees = allEmployees.filter (_.age> 100) mappa (_.name) 
oldEmployees: Lista [String] = Lista (Yoda)
Immaginate la raccolta di allEmployees che abbiamo è il set di risultati che abbiamo ottenuto da una query SQL simile a "SELECT * FROM Impiegati WHERE azienda = 'DevCode'". Ora siamo in grado di ordinare i dipendenti per azienda, trasformando la nostra lista [Employee] in una mappa in cui la chiave è il nome della società e il valore è un elenco di tutti i dipendenti che appartengono a tale società:
scala> val sortedEmployees = allEmployees.groupBy (_.company) 
sortedEmployees: scala.collection.immutable.Map [String, Lista [Employee]] = Map (DevCode - 
> List (dipendenti (Anna, 30, DevCode), dipendenti (ospite , 30, DevCode), 
dipendenti (Thomas, 41, DevCode)), LucasArt -> Lista (Employee (Luca, 30, LucasArt), 
dipendenti (Yoda, 800, LucasArt)))
Come esempio di processo di ogni ulteriore elenco memorizzato come un valore di questo (chiave-> valore) HashMap, potremmo immaginare di calcolo l'età media dei dipendenti per ogni azienda.
In pratica, questo significa che dobbiamo sommare il campo 'età' per tutti i dipendenti di ciascuna lista e dividerlo per il numero di dipendenti in tale elenco. Facciamo che prima solo per DevCode:
scala> devcodeEmployees 
res4: Lista [Employee] = Lista (Employee (Anna, 30, DevCode), dipendenti (guest, 30, DevCode), 
dipendenti (Thomas, 41, DevCode))
scala> val devcodeAges = devcodeEmployees.map (_.age) 
devcodeAges: Lista [Int.] = List (30, 30, 41)
scala> val devcodeAverageAge = devcodeAges.sum / devcodeAges.size 
devcodeAverageAge: Int = 33
Tornando al caso più generale della nostra Carta (tasto: String -> valore: Lista [dipendenti]), ora possiamo aggregare e calcolare l'età media per ogni azienda semplicemente scrivere un paio di righe:
scala> val averageAgeByCompany sortedEmployees.map = {case (chiave, valore) => 
   |. valore (0) copia (name = "media", età = (value.map (_.age) somma) / value.size.) } 
averageAgeByCompany: scala.collection.immutable.Iterable [Employee] = 
List (dipendenti (media, 33, DevCode), dipendenti (in media, 415, LucasArt))
Il "caso (chiave, valore)" è un esempio del meccanismo di modello molto potente di corrispondenza che fornisce Scala. Vedere la documentazione Scala per ulteriori chiarimenti.
Ora abbiamo finito. Quello che abbiamo appena scritto è un po '"Map Reduce" algoritmo. Dal momento che l'aggregazione dei dipendenti per ogni azienda è totalmente indipendente da altre aziende, questo algoritmo è molto semplice da parallelizzare.
Implementazioni equivalente dell'algoritmo, sia in Java e Scala, sono riportati in appendice.

Referenze

Il typesafe stack.

Appendice

Ridurre la mappa: Java
classe Impiegato pubblico {
              final String nome; 
              finale età Integer; 
              società String finale;
              dipendenti pubblici (String name, età Integer, società String) { 
                          this.name name = == null? "Guest": nome, 
                          età this.age = == null? 30: età; 
                          this.company = società == null? "DevCode": società; 
              }
              String getName pubblico () { 
                          return name; 
              }
              pubblico getAge int () { 
                          età di ritorno; 
              }
              public String getCompany () { 
                          società ritorno; 
              }
              @ Override 
              public String toString () { 
                            "Employee [name =" ritorno + nome + ", età =" + età + ", 
azienda = " 
                                                     + azienda + "]"; 
              } 
}
classe Builder { 
              String name, società; 
              età Integer;
              Builder (String name) { 
                          this.name = nome;               }


              Dipendente build () { 
                          ritorno new Employee (nome, età, società); 
              }
              Costruttore di età (età Integer) { 
                          this.age età =; 
                          return this; 
              }
              Builder società (società String) { 
                          this.company società =; 
                          return this; 
              } 
}
importazione java.util.ArrayList; 
java.util.Collection importazione; 
java.util.List importazione; 
com.google.common.base.Function importazione; 
com.google.common.collect.ImmutableListMultimap importazione; 
com.google.common importazione. collect.ImmutableSet; 
com.google.common.collect.Multimaps importazione;
public class {MapReduce
              public static final void main (String [] args) { 
                            ospite dipendenti = new Builder ("Guest") build ();. 
                            . dipendenti anna = new Builder ("Anna") build (); 
                            dipendenti thomas = new Builder ("Thomas "..) età (41) build (); 
                            dipendenti luke = new 
Builder ("Luca LucasArt") build ().; ") società (". 
                            Impiegato yoda = new 
Builder ("Yoda"), età (800).. . società ("LucasArt") build ();
                            Collezione <Employee> dipendenti = new





                            ImmutableListMultimap <String, Employee> 
personsGroupByCompany = Multimaps.index (dipendenti, new Function <dipendenti, 
String> () {                              String pubblici si applica (a persona per i dipendenti) {                                person.getCompany ritorno ();                              }                             });






                            ImmutableSet <String> companyNamesFromMap = 
personsGroupByCompany.keySet ();
                            Lista <Employee> averageAgeByCompany = new 
ArrayList <Employee> ();
                            for (String società: companyNamesFromMap) { 
                                         Lista <Employee> employeesForThisCompany = 
personsGroupByCompany.get (società); 
                                         int somma = 0; 
                                         per (dipendente dipendenti: 
employeesForThisCompany) { 
                                                    somma + =                        "+ AverageAgeByCompany);            } }







MapReduce.scala:
caso dei dipendenti di classe (name: String = "ospite", età: Int = 30, società: String = "DevCode")
MapReduce oggetto { 
 def main (args: Array [String]): Unità = {
  Val Guest = dipendenti () 
  val anna dipendenti = ("Anna") 
  = val thomas dipendenti ("Tommaso", 41) 
  val luke dipendenti = ("Luca", azienda = "LucasArt") 
  val = luke.copy Yoda ("Yoda ", età = 800)
  val allEmployees = Lista (Luca, anna, ospite, Yoda, thomas) 
  val sortedEmployees = allEmployees.groupBy (_.company) 
  val averageAgeByCompany sortedEmployees.map = {case (chiave, valore) => 
      valore (0). copia (nome = "media", età = (. value.map (_.age) somma) / value.size) 
  } 
  println ("Risultato:" + averageAgeByCompany) 
 } 
}


Nessun commento:

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.