learnxinyminutes-docs/el/scala.md
2024-12-08 20:37:52 -07:00

33 KiB
Raw Blame History

language contributors translators filename
Scala
George Petrov
http://github.com/petrovg
Dominic Bou-Samra
http://dbousamra.github.com
Geoff Liu
http://geoffliu.me
Vasilis Panagiotopoulos
https://github.com/billpcs/
learnscala-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) .

/*
  Τα σχόλια που επεκτείνονται σε πολλές γραμμές , όπως μπορείτε
  να δείτε , φαίνονται κάπως έτσι.
*/

// Εκτύπωση με νέα γραμμή στην επόμενη εκτύπωση
println("Hello world!")
println(10)

// Εκτύπωση χωρίς νέα γραμμή στην επόμενη εκτύπωση
print("Hello world")

// Η δήλωση μεταβλητών γίνεται χρησιμοποιώντας var ή val.
// Οι δηλώσεις val είναι αμετάβλητες, ενώ οι var είναι μεταβλητές.
// Η αμεταβλητότητα είναι συμφέρουσα και προσπαθούμε να την χρησιμοποιούμε.
val x = 10 // το x είναι τώρα 10
x = 20 // σφάλμα: αλλαγή σε val
var y = 10
y = 20  // το y είναι τώρα 20

/*
  Η Scala είναι στατικού τύπου γλώσσα, εν τούτοις προσέξτε ότι στις παραπάνω
  δηλώσεις , δεν προσδιορίσαμε κάποιον τύπο. Αυτό συμβαίνει λόγω ενός
  χαρακτηριστικού της Scala που λέγεται συμπερασματολογία τύπων. Στις 
  περισσότερες των περιπτώσεων, ο μεταγλωττιστής της Scala μπορεί να
  μαντέψει ποιος είναι ο τύπος μιας μεταβλητής. Μπορούμε να δηλώσουμε
  αναλυτικά τον τύπο μιας μεταβλητής ως εξής:
*/
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

// Η αριθμητική είναι όπως τα συνηθισμένα
1 + 1 // 2
2 - 1 // 1
5 * 3 // 15
6 / 2 // 3
6 / 4 // 1
6.0 / 4 // 1.5


/*
  Αξιολογώντας μια έκφραση στο REPL, σας δίνεται ο τύπος και 
  η τιμή του αποτελέσματος 
*/

1 + 7

/* Η παραπάνω γραμμή έχει το εξής αποτέλεσμα:

  scala> 1 + 7
  res29: Int = 8

  Αυτό σημαίνει ότι το αποτέλεσμα της αξιολόγησης του 1 + 7 είναι ένα αντικείμενο
  τύπου Int με τιμή 8

  Σημειώστε ότι το "res29" είναι ένα σειριακά δημιουργούμενο όνομα μεταβλητής
  για να αποθηκεύονται τα αποτελέσματα των εκφράσεων που έχετε πληκτρολογήσει 
  και συνεπώς η έξοδός σας μπορεί να διαφέρει.
*/

"Τα αλφαριθμητικά στην Scala περικλείονται από διπλά εισαγωγικά"
'a' // Ένας χαρακτήρας στην Scala
// res30: Char = a
// Αλφαριθημτικά με μονά εισαγωγικά δεν υφίστανται <= Αυτό θα προκαλέσει σφάλμα.

// Τα αλφαριθμητικά έχουν τις συνηθισμένες μεθόδους της Java ορισμένες πάνω τους.
"hello world".length
"hello world".substring(2, 6)
"hello world".replace("C", "3")

// Έχουν επίσης μερικές επιπλέον μεθόδους Scala. 
// Δείτε επίσης : 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"

// Παρατηρήστε την χρήση των '{', '}'
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  }

while (i < 10) {  println("i " + i); i+=1  }   // Ναι ξανά! Τι συνέβη; Γιατί;

i    // Εμφάνισε την τιμή του i. Σημειώστε ότι ένας βρόχος while είναι βρόχος
     // με την κλασική έννοια - εκτελείται σειριακά καθώς αλλάζει η μεταβλητή
     // του βρόχου. Το while είναι πολύ γρήγορο , γρηγορότερο απο τους βρόχους
     // της Java , αλλά η χρήση combinators και comprehensions όπως πιο πάνω ,
     // είναι πιο εύκολη στην κατανόηση και στην παραλληλοποίηση.

// Ένας βρόχος do while :
do {
  println("x is still less than 10");
  x += 1
} while (x < 10)

// Η αναδρομή ουράς είναι ένας ιδιωματικός τρόπος να κάνεις επαναλαμβανόμενα
// πράγματα στην Scala. Οι αναδρομικές συναρτήσεις απαιτούν να γραφτεί 
// ρητά ο τύπος που θα επιστρέψουν, αλλιώς ο μεταγλωττιστής δεν μπορεί 
// αλλιώς να τον συνάγει. Παρακάτω είναι μια συνάρτηση που επιστρέφει Unit.
def showNumbersInRange(a:Int, b:Int):Unit = {
  print(a)
  if (a < b)
    showNumbersInRange(a + 1, b)
}
showNumbersInRange(1,14)


// Έλεγχος Ροής

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 επιστρέφει το αποτέλεσμα 
                 // της ακέραιας διαίρεσης και το υπόλοιπο.

// Για να έχουμε πρόσβαση στα στοιχεία μιας πλειάδας, χρησιμοποιούμε το _._n
// όπου το n είναι ο δείκτης με βάση το 1 του στοιχείου.
val d = divideInts(10,3)

d._1

d._2


/////////////////////////////////////////////////
// 5. Αντικειμενοστραφής Προγραμματισμός
/////////////////////////////////////////////////

/*
  Ότι έχουμε κάνει ως τώρα σε αυτό το tutorial ήταν απλές εκφράσεις
  (τιμές, συναρτήσεις, κτλ.). Αυτές οι εκφράσεις βολεύουν όταν τις
  γράφουμε στο 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. Η διαφορά είναι παρόμοια με τις
// μεθόδους κλάσεων σε σχέση με στατικές μεθόδους σε άλλες γλώσσες.
// Προσέξτε ότι τα objects και οι κλάσεις μπορούν να έχουν το ίδιο όνομα.
object Dog {
  def allKnownBreeds = List("pitbull", "shepherd", "retriever")
  def createDog(breed: String) = new Dog(breed)
}

// Οι κλάσεις περίπτωσης (case classes) είναι που έχουν την επιπλέον 
// λειτουργικότητα ενσωματωμένη. Μιά συνήθης ερώτηση για αρχάριους στην
// Scala είναι πότε να χρησιμοποιούνται κλάσεις και πότε case κλάσεις.
// Γενικά οι κλάσεις τείνουν να εστιάζουν στην ενθυλάκωση, τον
// πολυμορφισμό και τη συμπεριφορά. Οι τιμές μέσα σε αυτές τις κλάσεις 
// τείνουν να είναι private , και μόνο οι μέθοδοι είναι εκτεθειμένες.
// Ο κύριος σκοπός των case classes είναι να κρατούν δεδομένα που είναι
// σταθερές(immutable). Συνήθως έχουν λίγες μεθόδους και οι μέθοδοι σπάνια
// έχουν παρενέργειες.
case class Person(name: String, phoneNumber: String)

// Δημιουργία ενός instance. Παρατηρήστε ότι τα case classes 
// δεν χρειάζονται την λέξη "new" .
val george = Person("George", "1234")
val kate = Person("Kate", "4567")

// Με τα case classes, παίρνεις μερικά προνόμια δωρεάν , όπως:
george.phoneNumber  // => "1234"

// Ελέγχεται η ισότητα για κάθε πεδίο (δεν χρειάζεται να
// κάνουμε 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)

// Αν το μπλοκ της ανώνυμης  συνάρτησης ΚΑΙ η συνάρτηση που εφαρμόζεται
// (στην περίπτωσή μας το 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

Twitter Scala school

The scala documentation

Try Scala in your browser

Join the Scala user group