2015-08-05 16:40:37 +03:00
|
|
|
|
---
|
|
|
|
|
language: Scala
|
|
|
|
|
contributors:
|
|
|
|
|
- ["George Petrov", "http://github.com/petrovg"]
|
|
|
|
|
- ["Dominic Bou-Samra", "http://dbousamra.github.com"]
|
|
|
|
|
- ["Geoff Liu", "http://geoffliu.me"]
|
|
|
|
|
translators:
|
|
|
|
|
- ["Vasilis Panagiotopoulos" , "https://github.com/billpcs/"]
|
|
|
|
|
filename: learnscala-gr.scala
|
|
|
|
|
lang: el-gr
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
Scala - Η επεκτάσιμη γλώσσα
|
|
|
|
|
|
|
|
|
|
```scala
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Προετοιμαστείτε:
|
|
|
|
|
|
|
|
|
|
1) Κατεβάστε την Scala - http://www.scala-lang.org/downloads
|
|
|
|
|
2) Κάνετε εξαγωγή στην επιθυμητή σας τοποθεσία και βάλτε τον υποφάκελο bin
|
|
|
|
|
στο path του συστήματος
|
|
|
|
|
3) Ξεκινήστε ένα scala REPL γράφοντας scala. Θα πρέπει να βλέπετε το prompt:
|
|
|
|
|
|
|
|
|
|
scala>
|
|
|
|
|
|
|
|
|
|
Αυτό είναι το αποκαλούμενο REPL (Read-Eval-Print Loop) *.
|
|
|
|
|
Μπορείτε να πληκτρολογήσετε οποιαδήποτε έγκυρη έκφραση σε Scala μέσα του ,
|
|
|
|
|
και το αποτέλεσμα θα τυπωθεί. Θα εξηγήσουμε πως μοιάζουν τα αρχεία της Scala
|
|
|
|
|
αργότερα μέσα στο tutorial , αλλά για τώρα ας αρχίσουμε με κάποια βασικά.
|
|
|
|
|
*[Βρόχος του Διάβασε - Αξιολόγησε - Τύπωσε]
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 1. Βασικές έννοιες
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Τα σχόλια μίας γραμμής ξεκινούν με δύο "/" (:forward slashes) .
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Τα σχόλια που επεκτείνονται σε πολλές γραμμές , όπως μπορείτε
|
2015-10-18 00:02:18 +03:00
|
|
|
|
να δείτε , φαίνονται κάπως έτσι.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
*/
|
|
|
|
|
|
2015-08-05 17:06:31 +03:00
|
|
|
|
// Εκτύπωση με νέα γραμμή στην επόμενη εκτύπωση
|
2015-08-05 16:40:37 +03:00
|
|
|
|
println("Hello world!")
|
|
|
|
|
println(10)
|
|
|
|
|
|
2015-08-05 17:06:31 +03:00
|
|
|
|
// Εκτύπωση χωρίς νέα γραμμή στην επόμενη εκτύπωση
|
2015-08-05 16:40:37 +03:00
|
|
|
|
print("Hello world")
|
|
|
|
|
|
|
|
|
|
// Η δήλωση μεταβλητών γίνεται χρησιμοποιώντας var ή val.
|
|
|
|
|
// Οι δηλώσεις val είναι αμετάβλητες, ενώ οι var είναι μεταβλητές.
|
|
|
|
|
// Η αμεταβλητότητα είναι συμφέρουσα και προσπαθούμε να την χρησιμοποιούμε.
|
|
|
|
|
val x = 10 // το x είναι τώρα 10
|
|
|
|
|
x = 20 // σφάλμα: αλλαγή σε val
|
|
|
|
|
var y = 10
|
|
|
|
|
y = 20 // το y είναι τώρα 20
|
|
|
|
|
|
|
|
|
|
/*
|
2015-10-18 00:02:18 +03:00
|
|
|
|
Η Scala είναι στατικού τύπου γλώσσα, εν τούτοις προσέξτε ότι στις παραπάνω
|
2015-08-05 16:40:37 +03:00
|
|
|
|
δηλώσεις , δεν προσδιορίσαμε κάποιον τύπο. Αυτό συμβαίνει λόγω ενός
|
|
|
|
|
χαρακτηριστικού της Scala που λέγεται συμπερασματολογία τύπων. Στις
|
2015-08-05 17:06:31 +03:00
|
|
|
|
περισσότερες των περιπτώσεων, ο μεταγλωττιστής της Scala μπορεί να
|
2015-10-18 00:02:18 +03:00
|
|
|
|
μαντέψει ποιος είναι ο τύπος μιας μεταβλητής. Μπορούμε να δηλώσουμε
|
|
|
|
|
αναλυτικά τον τύπο μιας μεταβλητής ως εξής:
|
2015-08-05 16:40:37 +03:00
|
|
|
|
*/
|
|
|
|
|
val z: Int = 10
|
|
|
|
|
val a: Double = 1.0
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Προσέξτε ότι υπάρχει αυτόματη μετατροπή από ακέραιο (Int) σε διπλής
|
|
|
|
|
ακρίβειας (Double), και συνεπώς το αποτέλεσμα είναι 10.0 και όχι 10.
|
|
|
|
|
*/
|
|
|
|
|
val b: Double = 10
|
|
|
|
|
|
|
|
|
|
// Λογικές τιμές
|
|
|
|
|
true
|
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
// Λογικές Πράξεις
|
|
|
|
|
!true // false
|
|
|
|
|
!false // true
|
|
|
|
|
true == false // false
|
|
|
|
|
10 > 5 // true
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Η αριθμητική είναι όπως τα συνηθισμένα
|
2015-08-05 16:40:37 +03:00
|
|
|
|
1 + 1 // 2
|
|
|
|
|
2 - 1 // 1
|
|
|
|
|
5 * 3 // 15
|
|
|
|
|
6 / 2 // 3
|
|
|
|
|
6 / 4 // 1
|
|
|
|
|
6.0 / 4 // 1.5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2015-08-05 17:06:31 +03:00
|
|
|
|
Αξιολογώντας μια έκφραση στο REPL, σας δίνεται ο τύπος και
|
2015-08-05 16:40:37 +03:00
|
|
|
|
η τιμή του αποτελέσματος
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
1 + 7
|
|
|
|
|
|
|
|
|
|
/* Η παραπάνω γραμμή έχει το εξής αποτέλεσμα:
|
|
|
|
|
|
|
|
|
|
scala> 1 + 7
|
|
|
|
|
res29: Int = 8
|
|
|
|
|
|
|
|
|
|
Αυτό σημαίνει ότι το αποτέλεσμα της αξιολόγησης του 1 + 7 είναι ένα αντικείμενο
|
|
|
|
|
τύπου Int με τιμή 8
|
|
|
|
|
|
|
|
|
|
Σημειώστε ότι το "res29" είναι ένα σειριακά δημιουργούμενο όνομα μεταβλητής
|
|
|
|
|
για να αποθηκεύονται τα αποτελέσματα των εκφράσεων που έχετε πληκτρολογήσει
|
|
|
|
|
και συνεπώς η έξοδός σας μπορεί να διαφέρει.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
"Τα αλφαριθμητικά στην Scala περικλείονται από διπλά εισαγωγικά"
|
|
|
|
|
'a' // Ένας χαρακτήρας στην Scala
|
|
|
|
|
// res30: Char = a
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Αλφαριθημτικά με μονά εισαγωγικά δεν υφίστανται <= Αυτό θα προκαλέσει σφάλμα.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
|
|
|
|
|
// Τα αλφαριθμητικά έχουν τις συνηθισμένες μεθόδους της Java ορισμένες πάνω τους.
|
|
|
|
|
"hello world".length
|
|
|
|
|
"hello world".substring(2, 6)
|
|
|
|
|
"hello world".replace("C", "3")
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Έχουν επίσης μερικές επιπλέον μεθόδους Scala.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// Δείτε επίσης : scala.collection.immutable.StringOps
|
|
|
|
|
"hello world".take(5)
|
|
|
|
|
"hello world".drop(5)
|
|
|
|
|
|
|
|
|
|
// Παρεμβολή αλφαριθμητικών : παρατηρήστε το πρόθεμα "s"
|
|
|
|
|
val n = 45
|
|
|
|
|
s"We have $n apples" // => "We have 45 apples"
|
|
|
|
|
|
2015-08-05 17:06:31 +03:00
|
|
|
|
// Παρατηρήστε την χρήση των '{', '}'
|
2015-08-05 16:40:37 +03:00
|
|
|
|
val a = Array(11, 9, 6)
|
|
|
|
|
s"My second daughter is ${a(0) - a(2)} years old." // => "My second daughter is 5 years old."
|
|
|
|
|
s"We have double the amount of ${n / 2.0} in apples." // => "We have double the amount of 22.5 in apples."
|
|
|
|
|
s"Power of 2: ${math.pow(2, 2)}" // => "Power of 2: 4"
|
|
|
|
|
|
|
|
|
|
// Μορφοποίηση με παρεμβεβλημένα αλφαριθμητικά με το πρόθεμα "f"
|
|
|
|
|
f"Power of 5: ${math.pow(5, 2)}%1.0f" // "Power of 5: 25"
|
|
|
|
|
f"Square root of 122: ${math.sqrt(122)}%1.4f" // "Square root of 122: 11.0454"
|
|
|
|
|
|
|
|
|
|
// Raw αλφαριθμητικά, που αγνοούν τους ειδικούς χαρακτήρες.
|
|
|
|
|
raw"New line feed: \n. Carriage return: \r." // => "New line feed: \n. Carriage return: \r."
|
|
|
|
|
|
|
|
|
|
// Μερικούς χαρακτήρες πρέπει να τους κάνουμε "escape",
|
|
|
|
|
// λ.χ ένα διπλό εισαγωγικό μέσα σε ένα αλφαριθμητικό :
|
|
|
|
|
"They stood outside the \"Rose and Crown\"" // => "They stood outside the "Rose and Crown""
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Τα τριπλά διπλά-εισαγωγικά επιτρέπουν στα αλφαριθμητικά να εκτείνονται σε
|
|
|
|
|
πολλαπλές γραμμές και να περιέχουν διπλά εισαγωγικά
|
|
|
|
|
*/
|
|
|
|
|
val html = """<form id="daform">
|
|
|
|
|
<p>Press belo', Joe</p>
|
|
|
|
|
<input type="submit">
|
|
|
|
|
</form>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 2. Συναρτήσεις
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Οι συναρτήσεις ορίζονται ως εξής:
|
|
|
|
|
//
|
|
|
|
|
// def functionName(args...): ReturnType = { body... }
|
|
|
|
|
//
|
|
|
|
|
// Αν προέρχεστε απο πιο παραδοσιακές γλώσσες (C/C++ , Java) παρατηρήστε
|
|
|
|
|
// την παράλειψη του return. Στην Scala , η τελευταία έκφραση στο μπλόκ
|
|
|
|
|
// της συνάρτησης είναι η τιμή που επιστρέφει η συνάρτηση.
|
|
|
|
|
def sumOfSquares(x: Int, y: Int): Int = {
|
|
|
|
|
val x2 = x * x
|
|
|
|
|
val y2 = y * y
|
|
|
|
|
x2 + y2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Τα { } μπορούν να παραλειφθούν αν η συνάρτηση αποτελείται απο μια απλή έκφραση:
|
|
|
|
|
def sumOfSquaresShort(x: Int, y: Int): Int = x * x + y * y
|
|
|
|
|
|
|
|
|
|
// Η σύνταξη για την κλήση συναρτήσεων είναι γνώριμη:
|
|
|
|
|
sumOfSquares(3, 4) // => 25
|
|
|
|
|
|
|
|
|
|
// Στις περισσότερες των περιπτώσεων (με τις αναδρομικές συναρτήσεις να αποτελούν
|
|
|
|
|
// την πιο αξιοπρόσεκτη εξαίρεση) , ο τύπος επιστροφής της συνάρτησης μπορεί να
|
|
|
|
|
// παραλειφθεί, και η ίδια συμπερασματολογία τύπων που είδαμε με τις μεταβλητές
|
|
|
|
|
// θα δουλεύει και με τους τύπους επιστροφής της συνάρτησης:
|
|
|
|
|
def sq(x: Int) = x * x // Ο μεταγλωττιστής μπορεί να μαντέψει ότι
|
|
|
|
|
// ο τύπος επιστροφής της συνάρτησης είναι Int
|
|
|
|
|
|
|
|
|
|
// Οι συναρτήσεις μπορούν να έχουν προκαθορισμένες τιμές:
|
|
|
|
|
def addWithDefault(x: Int, y: Int = 5) = x + y
|
|
|
|
|
addWithDefault(1, 2) // => 3
|
|
|
|
|
addWithDefault(1) // => 6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Οι ανώνυμες συναρτήσεις είναι ως εξής:
|
|
|
|
|
(x:Int) => x * x
|
|
|
|
|
|
|
|
|
|
// Σε αντίθεση με τα defs , ακόμα και ο τύπος εισόδου απο τις ανώνυμες
|
|
|
|
|
// συναρτήσεις μπορεί να παραληφθεί αν τα συμφραζόμενα το κάνουν ξεκάθαρο.
|
|
|
|
|
// Προσέξτε τον τύπο "Int => Int" που σημαίνει ότι μια συνάρτηση παίρνει
|
|
|
|
|
// ένα Int και επιστρέφει ένα Int.
|
|
|
|
|
val sq: Int => Int = x => x * x
|
|
|
|
|
|
|
|
|
|
// Οι ανώνυμες συναρτήσεις μπορούν να κληθούν όπως συνήθως:
|
|
|
|
|
sq(10) // => 100
|
|
|
|
|
|
|
|
|
|
// Αν κάθε όρισμα στην ανώνυμη συνάρτηση χρησιμοποιείται μόνο μία φορά,
|
|
|
|
|
// η Scala επιτρέπει έναν ακόμα πιο σύντομο τρόπο να οριστεί. Αυτές
|
|
|
|
|
// οι ανώνυμες συναρτήσεις αποδεικνύεται ότι είναι πολύ κοινές ,
|
|
|
|
|
// όπως θα γίνει προφανές στο μέρος των δομών δεδομένων.
|
|
|
|
|
val addOne: Int => Int = _ + 1
|
|
|
|
|
val weirdSum: (Int, Int) => Int = (_ * 2 + _ * 3)
|
|
|
|
|
|
|
|
|
|
addOne(5) // => 6
|
|
|
|
|
weirdSum(2, 4) // => 16
|
|
|
|
|
|
|
|
|
|
// Η δεσμευμένη λέξη return υπάρχει στην Scala , αλλά επιστρέφει μόνο
|
|
|
|
|
// από το πιο εσωτερικό def που την περικλείει.
|
|
|
|
|
// ΠΡΟΣΟΧΗ: Η χρήση του return στην Scala είναι επιρρεπής σε λάθη
|
|
|
|
|
// και θα πρέπει να αποφεύγεται.
|
|
|
|
|
// Δεν έχει καμία επίδραση στις ανώνυμες συναρτήσεις. Για παράδειγμα:
|
|
|
|
|
def foo(x: Int): Int = {
|
|
|
|
|
val anonFunc: Int => Int = { z =>
|
|
|
|
|
if (z > 5)
|
|
|
|
|
return z // Αυτή η σειρά κάνει το z την τιμή που επιστρέφει η foo!
|
|
|
|
|
else
|
|
|
|
|
z + 2 // Αυτή η γραμμή είναι η τιμή που επιστρέφει η anonFunc
|
|
|
|
|
}
|
|
|
|
|
anonFunc(x) // Αυτή η γραμμή είναι η τιμή που επιστρέφει η foo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 3. Έλεγχος ροής
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
1 to 5
|
|
|
|
|
val r = 1 to 5
|
|
|
|
|
r.foreach( println )
|
|
|
|
|
|
|
|
|
|
r foreach println
|
|
|
|
|
// ΠΡΟΣΟΧΗ: Η Scala είναι σχετικά επιεικής ως αναφορά τις τελείες και
|
|
|
|
|
// τις παρενθέσεις. Διαβάστε τους κανόνες ξεχωριστά.
|
|
|
|
|
// Αυτό βοηθάει στο να γράφεις DSLs και APIs που διαβάζονται σαν τα Αγγλικά.
|
|
|
|
|
|
|
|
|
|
(5 to 1 by -1) foreach ( println )
|
|
|
|
|
|
|
|
|
|
// Ένας βρόχος while :
|
|
|
|
|
var i = 0
|
|
|
|
|
while (i < 10) { println("i " + i); i+=1 }
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
while (i < 10) { println("i " + i); i+=1 } // Ναι ξανά! Τι συνέβη; Γιατί;
|
2015-08-05 16:40:37 +03:00
|
|
|
|
|
|
|
|
|
i // Εμφάνισε την τιμή του i. Σημειώστε ότι ένας βρόχος while είναι βρόχος
|
|
|
|
|
// με την κλασική έννοια - εκτελείται σειριακά καθώς αλλάζει η μεταβλητή
|
|
|
|
|
// του βρόχου. Το while είναι πολύ γρήγορο , γρηγορότερο απο τους βρόχους
|
|
|
|
|
// της Java , αλλά η χρήση combinators και comprehensions όπως πιο πάνω ,
|
|
|
|
|
// είναι πιο εύκολη στην κατανόηση και στην παραλληλοποίηση.
|
|
|
|
|
|
|
|
|
|
// Ένας βρόχος do while :
|
|
|
|
|
do {
|
|
|
|
|
println("x is still less than 10");
|
|
|
|
|
x += 1
|
|
|
|
|
} while (x < 10)
|
|
|
|
|
|
|
|
|
|
// Η αναδρομή ουράς είναι ένας ιδιωματικός τρόπος να κάνεις επαναλαμβανόμενα
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// πράγματα στην Scala. Οι αναδρομικές συναρτήσεις απαιτούν να γραφτεί
|
|
|
|
|
// ρητά ο τύπος που θα επιστρέψουν, αλλιώς ο μεταγλωττιστής δεν μπορεί
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// αλλιώς να τον συνάγει. Παρακάτω είναι μια συνάρτηση που επιστρέφει Unit.
|
|
|
|
|
def showNumbersInRange(a:Int, b:Int):Unit = {
|
|
|
|
|
print(a)
|
|
|
|
|
if (a < b)
|
|
|
|
|
showNumbersInRange(a + 1, b)
|
|
|
|
|
}
|
|
|
|
|
showNumbersInRange(1,14)
|
|
|
|
|
|
|
|
|
|
|
2015-08-05 17:06:31 +03:00
|
|
|
|
// Έλεγχος Ροής
|
2015-08-05 16:40:37 +03:00
|
|
|
|
|
|
|
|
|
val x = 10
|
|
|
|
|
|
|
|
|
|
if (x == 1) println("yeah")
|
|
|
|
|
if (x == 10) println("yeah")
|
|
|
|
|
if (x == 11) println("yeah")
|
|
|
|
|
if (x == 11) println ("yeah") else println("nay")
|
|
|
|
|
|
|
|
|
|
println(if (x == 10) "yeah" else "nope")
|
|
|
|
|
val text = if (x == 10) "yeah" else "nope"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 4. Δομές Δεδομένων
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
val a = Array(1, 2, 3, 5, 8, 13)
|
|
|
|
|
a(0)
|
|
|
|
|
a(3)
|
|
|
|
|
a(21) // "Πετάει" exception
|
|
|
|
|
|
|
|
|
|
val m = Map("fork" -> "tenedor", "spoon" -> "cuchara", "knife" -> "cuchillo")
|
|
|
|
|
m("fork")
|
|
|
|
|
m("spoon")
|
|
|
|
|
m("bottle") // "Πετάει" exception
|
|
|
|
|
|
|
|
|
|
val safeM = m.withDefaultValue("no lo se")
|
|
|
|
|
safeM("bottle")
|
|
|
|
|
|
|
|
|
|
val s = Set(1, 3, 7)
|
|
|
|
|
s(0)
|
|
|
|
|
s(1)
|
|
|
|
|
|
|
|
|
|
/* Δείτε το documentation του map εδώ -
|
|
|
|
|
* http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Map
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Πλειάδες
|
|
|
|
|
|
|
|
|
|
(1, 2)
|
|
|
|
|
|
|
|
|
|
(4, 3, 2)
|
|
|
|
|
|
|
|
|
|
(1, 2, "three")
|
|
|
|
|
|
|
|
|
|
(a, 2, "three")
|
|
|
|
|
|
|
|
|
|
// Γιατί να το έχουμε αυτό;
|
|
|
|
|
val divideInts = (x:Int, y:Int) => (x / y, x % y)
|
|
|
|
|
|
|
|
|
|
divideInts(10,3) // Η συνάρτηση divideInts επιστρέφει το αποτέλεσμα
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// της ακέραιας διαίρεσης και το υπόλοιπο.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
|
|
|
|
|
// Για να έχουμε πρόσβαση στα στοιχεία μιας πλειάδας, χρησιμοποιούμε το _._n
|
|
|
|
|
// όπου το n είναι ο δείκτης με βάση το 1 του στοιχείου.
|
|
|
|
|
val d = divideInts(10,3)
|
|
|
|
|
|
|
|
|
|
d._1
|
|
|
|
|
|
|
|
|
|
d._2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 5. Αντικειμενοστραφής Προγραμματισμός
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Ότι έχουμε κάνει ως τώρα σε αυτό το tutorial ήταν απλές εκφράσεις
|
2015-10-18 00:02:18 +03:00
|
|
|
|
(τιμές, συναρτήσεις, κτλ.). Αυτές οι εκφράσεις βολεύουν όταν τις
|
2015-08-05 16:40:37 +03:00
|
|
|
|
γράφουμε στο REPL για γρήγορες δοκιμές, αλλά δεν μπορούν να υπάρχουν
|
|
|
|
|
από μόνες τους σε ένα αρχείο Scala. Για παράδειγμα , δεν μπορούμε να
|
|
|
|
|
έχουμε μόνο ένα "val x = 5" στο αρχείο Scala. Αντί αυτού , τα μόνα
|
|
|
|
|
στοιχεία του πάνω επιπέδου που επιτρέπονται στην Scala είναι:
|
|
|
|
|
|
|
|
|
|
- αντικείμενα (objects)
|
|
|
|
|
- κλάσεις (classes)
|
|
|
|
|
- κλάσεις περίπτωσης (case classes στην Scala)
|
|
|
|
|
- Χαρακτηριστικά (traits , όπως ονομάζονται στην Scala)
|
|
|
|
|
|
|
|
|
|
Και τώρα θα εξηγήσουμε τι είναι αυτά.
|
|
|
|
|
*/
|
|
|
|
|
// Οι κλάσεις είναι παρόμοιες με τις κλάσεις σε άλλες γλώσσες. Τα ορίσματα του
|
|
|
|
|
// "κατασκευαστή" (constructor) δηλώνονται μετά από το όνομα της κλάσης ,
|
|
|
|
|
// και η αρχικοποιήση γίνεται μέσα στο σώμα της κλάσης.
|
|
|
|
|
class Dog(br: String) {
|
|
|
|
|
// Κώδικας για τον "κατασκευαστή"
|
|
|
|
|
var breed: String = br
|
|
|
|
|
|
|
|
|
|
// Ορίζεται μια μέθοδος bark , που επιστρέφει ένα αλφαριθμητικό
|
|
|
|
|
def bark = "Woof, woof!"
|
|
|
|
|
|
|
|
|
|
// Οι τιμές και οι μέθοδοι είναι public εκτός αν χρησιμοποιήσουμε κάποια
|
|
|
|
|
// απο τις λέξεις κλειδιά "protected" και "private" .
|
|
|
|
|
private def sleep(hours: Int) =
|
|
|
|
|
println(s"I'm sleeping for $hours hours")
|
|
|
|
|
|
|
|
|
|
// Οι abstract μέθοδοι είναι απλά μέθοδοι χωρίς σώμα. Αν βγάζαμε
|
|
|
|
|
// το σχόλιο απο την επόμενη γραμμή η κλάση Dog θα έπρεπε να
|
|
|
|
|
// δηλωθεί ως abstract class Dog(...) { ... } :
|
|
|
|
|
// def chaseAfter(what: String): String
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val mydog = new Dog("greyhound")
|
|
|
|
|
println(mydog.breed) // => "greyhound"
|
|
|
|
|
println(mydog.bark) // => "Woof, woof!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Η λέξη "object" δημιουργεί ένα type ΚΑΙ ένα singleton instance αυτού.
|
|
|
|
|
// Είναι κοινό για τις κλάσεις στην Scala να έχουν ένα "συντροφικό object",
|
|
|
|
|
// όπου η συμπεριφορά για κάθε instance αιχμαλωτίζεται μέσα στις κλάσεις
|
|
|
|
|
// αυτές καθ' αυτές, αλλά η συμπρεριφορά που σχετίζεται με όλα τα instances
|
|
|
|
|
// της κλάσης πάνε μέσα στο object. Η διαφορά είναι παρόμοια με τις
|
|
|
|
|
// μεθόδους κλάσεων σε σχέση με στατικές μεθόδους σε άλλες γλώσσες.
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Προσέξτε ότι τα objects και οι κλάσεις μπορούν να έχουν το ίδιο όνομα.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
object Dog {
|
|
|
|
|
def allKnownBreeds = List("pitbull", "shepherd", "retriever")
|
|
|
|
|
def createDog(breed: String) = new Dog(breed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Οι κλάσεις περίπτωσης (case classes) είναι που έχουν την επιπλέον
|
|
|
|
|
// λειτουργικότητα ενσωματωμένη. Μιά συνήθης ερώτηση για αρχάριους στην
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Scala είναι πότε να χρησιμοποιούνται κλάσεις και πότε case κλάσεις.
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// Γενικά οι κλάσεις τείνουν να εστιάζουν στην ενθυλάκωση, τον
|
|
|
|
|
// πολυμορφισμό και τη συμπεριφορά. Οι τιμές μέσα σε αυτές τις κλάσεις
|
|
|
|
|
// τείνουν να είναι private , και μόνο οι μέθοδοι είναι εκτεθειμένες.
|
|
|
|
|
// Ο κύριος σκοπός των case classes είναι να κρατούν δεδομένα που είναι
|
|
|
|
|
// σταθερές(immutable). Συνήθως έχουν λίγες μεθόδους και οι μέθοδοι σπάνια
|
|
|
|
|
// έχουν παρενέργειες.
|
|
|
|
|
case class Person(name: String, phoneNumber: String)
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Δημιουργία ενός instance. Παρατηρήστε ότι τα case classes
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// δεν χρειάζονται την λέξη "new" .
|
|
|
|
|
val george = Person("George", "1234")
|
|
|
|
|
val kate = Person("Kate", "4567")
|
|
|
|
|
|
|
|
|
|
// Με τα case classes, παίρνεις μερικά προνόμια δωρεάν , όπως:
|
|
|
|
|
george.phoneNumber // => "1234"
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Ελέγχεται η ισότητα για κάθε πεδίο (δεν χρειάζεται να
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// κάνουμε override στο .equals)
|
|
|
|
|
Person("George", "1234") == Person("Kate", "1236") // => false
|
|
|
|
|
|
|
|
|
|
// Έυκολος τρόπος να κάνουμε αντιγραφή. Δημιουργούμε έναν νέο geroge:
|
|
|
|
|
// otherGeorge == Person("george", "9876")
|
|
|
|
|
val otherGeorge = george.copy(phoneNumber = "9876")
|
|
|
|
|
|
|
|
|
|
// Και πολλά άλλα. Τα case classes έχουν και αντιστοίχιση προτύπων
|
|
|
|
|
// (pattern matching) δωρεάν, δείτε παρακάτω.
|
|
|
|
|
|
|
|
|
|
// Τα χαρακτηριστικά (traits) έρχονται σε λίγο καιρό !
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 6. Αντιστοίχιση Προτύπων
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Η αντιστοίχιση προτύπων (pattern matching) είναι ένα πολύ δυνατό και
|
|
|
|
|
// ευρέως χρησιμοποιούμενο χαρακτηριστικό στην Scala. Παρακάτω βλέπουμε
|
|
|
|
|
// πως γίνεται το pattern matching σε ένα case class. Σημείωση: Σε
|
|
|
|
|
// αντίθεση με άλλες γλώσσες η Scala δεν χρειάζεται breaks, γιατί γίνεται
|
|
|
|
|
// αυτόματα όταν γίνει κάποιο match.
|
|
|
|
|
|
|
|
|
|
def matchPerson(person: Person): String = person match {
|
|
|
|
|
// Μετά προσδιορίζουμε το πρότυπο (pattern):
|
|
|
|
|
case Person("George", number) => "We found George! His number is " + number
|
|
|
|
|
case Person("Kate", number) => "We found Kate! Her number is " + number
|
|
|
|
|
case Person(name, number) => "We matched someone : " + name + ", phone : " + number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val email = "(.*)@(.*)".r // Ορίζουμε ένα regex για το επόμενο παράδειγμα.
|
|
|
|
|
// (regex <- REGular EXpression)
|
|
|
|
|
|
|
|
|
|
// Το pattern matching μπορεί να μοιάζει γνώριμο απο τα switch statements σε
|
|
|
|
|
// γλώσσες που ανήκουν στην οικογένεια της C αλλά είναι πολύ πιο ισχυρό.
|
|
|
|
|
// Στην Scala , μπορούμε να κάνουμε match πολύ περισσότερα:
|
|
|
|
|
def matchEverything(obj: Any): String = obj match {
|
|
|
|
|
// Μπορούμε να ταιριάξουμε τιμές:
|
|
|
|
|
case "Hello world" => "Got the string Hello world"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε τύπους:
|
|
|
|
|
case x: Double => "Got a Double: " + x
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να βάλουμε συνθήκες:
|
|
|
|
|
case x: Int if x > 10000 => "Got a pretty big number!"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε case classes όπως πρίν:
|
|
|
|
|
case Person(name, number) => s"Got contact info for $name!"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε regex:
|
|
|
|
|
case email(name, domain) => s"Got email address $name@$domain"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε πλειάδες:
|
|
|
|
|
case (a: Int, b: Double, c: String) => s"Got a tuple: $a, $b, $c"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε δομές δεδομένων:
|
|
|
|
|
case List(1, b, c) => s"Got a list with three elements and starts with 1: 1, $b, $c"
|
|
|
|
|
|
|
|
|
|
// Μπορούμε να ταιριάξουμε πρότυπα που το ένα είναι μέσα στο άλλο:
|
|
|
|
|
case List(List((1, 2,"YAY"))) => "Got a list of list of tuple"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Στην πραγματικότητα , μπορούμε να κάνουμε pattern matching σε όποιο αντικείμενο
|
|
|
|
|
// έχει την μέθοδο "unapply". Αυτό το χαρακτηριστικό είναι τόσο ισχυρό ώστε
|
|
|
|
|
// η Scala επιτρέπει να ορίστούν ολόκληρες συναρτήσεις σαν patterns.
|
|
|
|
|
val patternFunc: Person => String = {
|
|
|
|
|
case Person("George", number) => s"George's number: $number"
|
|
|
|
|
case Person(name, number) => s"Random person's number: $number"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 7. Συναρτησιακός Προγραμματισμός
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Η Scala επιτρέπει στις μεθόδους και τις συναρτήσεις να επιστρέφουν ή να
|
|
|
|
|
// δέχονται ως παραμέτρους άλλες μεθόδους ή συναρτήσεις.
|
|
|
|
|
|
|
|
|
|
val add10: Int => Int = _ + 10 // Μια συνάρτηση που δέχεται Int και επιστρέφει Int
|
|
|
|
|
List(1, 2, 3) map add10 // List(11, 12, 13) - το add10 εφαρμόζεται σε κάθε στοιχείο
|
|
|
|
|
// μέσω του map
|
|
|
|
|
|
|
|
|
|
// Οι ανώνυμες συναρτήσεις μπορούν να χρησιμοποιηθούν αντί
|
|
|
|
|
// ονοματισμένων (όπως απο πάνω) :
|
|
|
|
|
List(1, 2, 3) map (x => x + 10)
|
|
|
|
|
|
|
|
|
|
// Και το σύμβολο της κάτω παύλας , μπορεί να χρησιμοποιηθεί αν υπάρχει μόνο
|
|
|
|
|
// ένα όρισμα στην ανώνυμη συνάρτηση. Έτσι δεσμεύεται ως η μεταβλητή.
|
|
|
|
|
List(1, 2, 3) map (_ + 10)
|
|
|
|
|
|
2015-10-18 00:02:18 +03:00
|
|
|
|
// Αν το μπλοκ της ανώνυμης συνάρτησης ΚΑΙ η συνάρτηση που εφαρμόζεται
|
2015-08-05 16:40:37 +03:00
|
|
|
|
// (στην περίπτωσή μας το foreach και το println) παίρνουν ένα όρισμα
|
|
|
|
|
// μπορείτε να παραλείψετε την κάτω παύλα.
|
|
|
|
|
List("Dom", "Bob", "Natalia") foreach println
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Συνδυαστές
|
|
|
|
|
|
|
|
|
|
s.map(sq)
|
|
|
|
|
|
|
|
|
|
val sSquared = s. map(sq)
|
|
|
|
|
|
|
|
|
|
sSquared.filter(_ < 10)
|
|
|
|
|
|
|
|
|
|
sSquared.reduce (_+_)
|
|
|
|
|
|
|
|
|
|
// Η συνάρτηση filter παίρνει ένα κατηγορούμενο (predicate)
|
|
|
|
|
// που είναι μια συνάρτηση απο το A -> Boolean και διαλέγει
|
|
|
|
|
// όλα τα στοιχεία που ικανοποιούν αυτό το κατηγορούμενο.
|
|
|
|
|
List(1, 2, 3) filter (_ > 2) // List(3)
|
|
|
|
|
case class Person(name:String, age:Int)
|
|
|
|
|
List(
|
|
|
|
|
Person(name = "Dom", age = 23),
|
|
|
|
|
Person(name = "Bob", age = 30)
|
|
|
|
|
).filter(_.age > 25) // List(Person("Bob", 30))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Το foreach είναι μια μέθοδος της Scala , που ορίζεται για ορισμένες
|
|
|
|
|
// συλλογές (collections). Παίρνει έναν τύπο και επιστρέφει Unit
|
|
|
|
|
// (μια μέθοδο void)
|
|
|
|
|
val aListOfNumbers = List(1, 2, 3, 4, 10, 20, 100)
|
|
|
|
|
aListOfNumbers foreach (x => println(x))
|
|
|
|
|
aListOfNumbers foreach println
|
|
|
|
|
|
|
|
|
|
// For comprehensions
|
|
|
|
|
|
|
|
|
|
for { n <- s } yield sq(n)
|
|
|
|
|
|
|
|
|
|
val nSquared2 = for { n <- s } yield sq(n)
|
|
|
|
|
|
|
|
|
|
for { n <- nSquared2 if n < 10 } yield n
|
|
|
|
|
|
|
|
|
|
for { n <- s; nSquared = n * n if nSquared < 10} yield nSquared
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Προσοχή : Αυτά δεν ήταν βρόχοι for. Η σημασιολογία ενός βρόχου for είναι
|
|
|
|
|
η επανάληψη, ενώ ένα for-comprehension ορίζει μια σχέση μεταξύ δύο
|
|
|
|
|
συνόλων δεδομένων.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 8. Implicits
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
/*
|
|
|
|
|
ΠΡΟΣΟΧΗ! Τα implicits είναι ένα σύνολο απο ισχυρά χαρακτηριστικά της Scala
|
|
|
|
|
και επομένως είναι εύκολο να γίνει κατάχρηση. Οι αρχάριοι στην Scala θα
|
|
|
|
|
πρέπει να αντισταθούν στον πειρασμό να τα χρησιμοποιήσουν έως ότου, όχι
|
|
|
|
|
μόνο καταλάβουν πως λειτουργούν, αλλά ακόμα εξασκηθούν πάνω τους.
|
|
|
|
|
Ο μόνος λόγος που συμπεριλάβαμε αυτό το κομμάτι στο tutorial είναι
|
|
|
|
|
γιατί είναι τόσο κοινό στις βιβλιοθήκες της Scala , που αδύνατο να κάνεις
|
|
|
|
|
οτιδήποτε σημαντικό χωρίς να χρησιμοποιήσεις μια που να έχει implicits.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Κάθε τιμή (vals , συναρτήσεις , αντικείμενα , κτλ) μπορεί να δηλωθεί ως
|
|
|
|
|
// implicit χρησιμοποιώντας , ναι το μαντέψατε , την λέξη "implicit".
|
|
|
|
|
// Σημειώστε ότι χρησιμοποιούμε την κλάση Dog που δημιουργήσαμε στο
|
|
|
|
|
// 5ο μέρος των παραδειγμάτων.
|
|
|
|
|
implicit val myImplicitInt = 100
|
|
|
|
|
implicit def myImplicitFunction(breed: String) = new Dog("Golden " + breed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Απο μόνη της, η λέξη implicit, δεν αλλάζει την συμπεριφορά μιάς τιμής
|
|
|
|
|
// οπότε οι παραπάνω μπορούν να χρησιμοποιοηθούν όπως συνήθως.
|
|
|
|
|
myImplicitInt + 2 // => 102
|
|
|
|
|
myImplicitFunction("Pitbull").breed // => "Golden Pitbull"
|
|
|
|
|
|
|
|
|
|
// Η διαφορά είναι ότι τώρα αυτές οι τιμές έχουν την δυνατότητα να
|
|
|
|
|
// χρησιμοποιηθούν όταν ένα άλλο κομμάτι κώδικα "χρειάζεται" μια
|
|
|
|
|
// implicit τιμή. Μια τέτοια περίπτωση είναι τα ορίσματα μιας implicit
|
|
|
|
|
// συνάρτησης:
|
|
|
|
|
def sendGreetings(toWhom: String)(implicit howMany: Int) =
|
|
|
|
|
s"Hello $toWhom, $howMany blessings to you and yours!"
|
|
|
|
|
|
|
|
|
|
// Άν τροφοδοτήσουμε μια τιμή για το "homMany", η συνάρτηση συμπεριφέρεται
|
|
|
|
|
// ως συνήθως
|
|
|
|
|
sendGreetings("John")(1000) // => "Hello John, 1000 blessings to you and yours!"
|
|
|
|
|
|
|
|
|
|
// Αλλά αν παραλείψουμε την παράμετρο implicit , μια implicit τιμή του ιδίου τύπου
|
|
|
|
|
// χρησιμοποιείται, στην περίπτωσή μας, το "myImplicitInt"
|
|
|
|
|
sendGreetings("Jane") // => "Hello Jane, 100 blessings to you and yours!"
|
|
|
|
|
|
|
|
|
|
// Οι παράμετροι implicit συναρτήσεων μας επιτρέπουν να προσομοιάζουμε
|
|
|
|
|
// κλάσεις τύπων (type classes) σε άλλες συναρτησιακές γλώσσες.
|
|
|
|
|
// Χρησιμοποιείται τόσο συχνά που έχει την δικιά του συντομογραφία.
|
|
|
|
|
// Οι επόμενες δύο γραμμές κώδικα σημαίνουν το ίδιο πράγμα.
|
|
|
|
|
def foo[T](implicit c: C[T]) = ...
|
|
|
|
|
def foo[T : C] = ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Μια άλλη περίπτωση στην οποία ο μεταγλωττιστής αναζητά μια implicit τιμή
|
|
|
|
|
// είναι αν έχετε obj.method (...)
|
|
|
|
|
// αλλά το "obj" δεν έχει την "method" ως μέθοδο. Σε αυτή την περίπτωση,
|
|
|
|
|
// αν υπάρχει μια implicit μετατροπή του τύπου Α => Β, όπου Α είναι ο τύπος
|
|
|
|
|
// του obj, ενώ το Β έχει μία μέθοδο που ονομάζεται «method», εφαρμόζεται η
|
|
|
|
|
// εν λόγω μετατροπή. Έτσι, έχοντας την MyImplicitFunction μέσα στο πεδίο
|
|
|
|
|
// εφαρμογής(scope), μπορούμε να πούμε:
|
|
|
|
|
"Retriever".breed // => "Golden Retriever"
|
|
|
|
|
"Sheperd".bark // => "Woof, woof!"
|
|
|
|
|
|
|
|
|
|
// Εδώ το String αρχικά μετατρέπεται σε Dog χρησιμοποιώντας την συνάρτησή μας
|
|
|
|
|
// παραπάνω, και μετά καλείται η κατάλληλη μέθοδος. Αυτό είναι ένα εξερετικά
|
|
|
|
|
// ισχυρό χαρακτηριστικό, αλλά δεν πρέπει να χρησιμοποιείται με ελαφριά την
|
|
|
|
|
// καρδιά. Μάλιστα, όταν ορίσατε την συνάρτηση implicit παραπάνω, ο μεταγλωττιστής
|
|
|
|
|
// θα πρέπει να σας έδωσε μια προειδοποιήση, ότι δεν πρέπει να το κάνετε αυτό
|
|
|
|
|
// εκτός αν πραγματικά γνωρίζετε τι κάνετε.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
// 9. Διάφορα
|
|
|
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Εισαγωγή βιβλιοθηκών κτλ
|
|
|
|
|
import scala.collection.immutable.List
|
|
|
|
|
|
|
|
|
|
// Εισαγωγή των πάντων απο το scala.collection.immutable
|
|
|
|
|
import scala.collection.immutable._
|
|
|
|
|
|
|
|
|
|
// Εισαγωγή πολλών κλάσεων σε μία έκφραση
|
|
|
|
|
import scala.collection.immutable.{List, Map}
|
|
|
|
|
|
|
|
|
|
// Δώστε ένα νέο όνομα στην εισαγωγή σας χρησιμοποιώντας το '=>'
|
|
|
|
|
import scala.collection.immutable.{ List => ImmutableList }
|
|
|
|
|
|
|
|
|
|
// Εισαγωγή όλων των κλάσεων εκτός απο μερικές.
|
|
|
|
|
// Το επόμενο δεν εισάγει το Map και το Set:
|
|
|
|
|
import scala.collection.immutable.{Map => _, Set => _, _}
|
|
|
|
|
|
|
|
|
|
// Το σημείο εισαγωγής του προγράμματος σας ορίζεται σε ένα αρχείο scala ,
|
|
|
|
|
// χρησιμοποιώντας ένα αντικείμενο (object), με μία μέθοδο , την main.
|
|
|
|
|
object Application {
|
|
|
|
|
def main(args: Array[String]): Unit = {
|
|
|
|
|
// Εδω γράφουμε ...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Files can contain multiple classes and objects. Compile with scalac
|
|
|
|
|
// Τα files μπορούν να περιέχουν περισσότερες απο μία κλάσεις και
|
|
|
|
|
// αντικείμενα. Το compile γίνεται με την εντολή scalac
|
|
|
|
|
|
|
|
|
|
// Εισαγωγή και εξαγωγή.
|
|
|
|
|
|
|
|
|
|
// Για να διβάσετε ένα αρχείο γραμμή προς γραμμή
|
|
|
|
|
import scala.io.Source
|
|
|
|
|
for(line <- Source.fromFile("myfile.txt").getLines())
|
|
|
|
|
println(line)
|
|
|
|
|
|
|
|
|
|
// Για να γράψετε σε ένα αρχείο
|
|
|
|
|
val writer = new PrintWriter("myfile.txt")
|
|
|
|
|
writer.write("Writing line for line" + util.Properties.lineSeparator)
|
|
|
|
|
writer.write("Another line here" + util.Properties.lineSeparator)
|
|
|
|
|
writer.close()
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Further resources
|
|
|
|
|
|
|
|
|
|
[Scala for the impatient](http://horstmann.com/scala/)
|
|
|
|
|
|
|
|
|
|
[Twitter Scala school](http://twitter.github.io/scala_school/)
|
|
|
|
|
|
|
|
|
|
[The scala documentation](http://docs.scala-lang.org/)
|
|
|
|
|
|
|
|
|
|
[Try Scala in your browser](http://scalatutorials.com/tour/)
|
|
|
|
|
|
|
|
|
|
Join the [Scala user group](https://groups.google.com/forum/#!forum/scala-user)
|
|
|
|
|
|