Γνωριμία με τη Scala

dimitris | Πέμ, 08/18/2011 - 23:54 | 32'

Σε αυτό το άρθρο παρουσιάζουμε τη Scala, μια γλώσσα για να κάνετε τις εφαρμογές και τους πειραματισμού σας με πολύ παραγωγικό τρόπο.

Του Χρήστου ΚΚ Λοβέρδου


Η Scala είναι μια σχετικά νέα γλώσσα προγραμματισμού, που συνδυάζει μοναδικά τον αντικειμενοστραφή (object-oriented) και το συναρτησιακό (functional) προγραμματισμό και εκτελείται στο ευρέως διαδεδομένο περιβάλλον μιας JVM (Java Virtual Machine). Αν θέλαμε να την κατατάξουμε μάλιστα στα είδη προγραμματισμού, μπορούμε να πούμε ότι ανήκει σε ένα υβριδικό προγραμματιστικό παράδειγμα, που αποδίδεται με τον αγγλικό όρο object-functional.

Ιστορία

Η πρώτη έκδοση της Scala δημιουργήθηκε από τον Martin Odersky το 2001 στο EPFL (École Polytechnique Fédérale de Lausanne) της Ελβετίας, όπου είναι καθηγητής πληροφορικής. Για τους δύσπιστους που μπορεί να αναρωτιούνται αν το παραγόμενο αποτέλεσμα είναι μια ακόμα "ακαδημαϊκή" γλώσσα χωρίς μεγάλη επαφή με το σκληρό κόσμου του προγραμματισμού σε εταιρικά περιβάλλοντα, αξίζει να αναφέρουμε ότι ο καθηγητής Odersky προσλήφθηκε από τη Sun (πλέον Oracle), για να φτιάξει μια βελτιωμένη έκδοση του μεταγλωττιστή (compiler) της Java. Υπήρξε μαθητής του περίφημου Niklaus Wirth, πατέρα της Pascal. Ο Martin Odersky, μπόρεσε και πάντρεψε στη Scala τις παιδαγωγικές κατευθύνσεις της Pascal με τον πραγματισμό της Java!

Μετά από δοκιμές με την "ακαδημαϊκή", κατά δική του ομολογία, γλώσσα Funnel, αποφάσισε να περάσει στο επόμενο στάδιο, παντρεύοντας δοκιμασμένες ιδέες με μερικά νέα χαρακτηριστικά. Έτσι γεννήθηκε η Scala. Στόχος της γλώσσας από την αρχή ήταν να συγκεράσει τα πλεονεκτήματα του αντικειμενοστραφούς και συναρτησιακού προγραμματισμού. Επίσης, να μπορεί να επαναχρησιμοποιεί υπάρχουσες βιβλιοθήκες κώδικα γραμμένες για τη JVM, ώστε να μη χρειάζεται να γράφουμε τα πάντα από την αρχή.

Το αποτέλεσμα είναι ότι μπορούμε να επαναχρησιμοποιήσουμε όλες τις Java βιβλιοθήκες μας μέσα από το πρόγραμμά μας, που είναι γραμμένο σε Scala. Η Scala μεταγλωτίζεται στις ακριβείς εντολές που μεταγλωτίζεται και η Java (αποκαλούνται bytecodes), με αποτέλεσμα να τρέχει με την πλήρη ταχύτητα που μπορεί να τρέχει μια γλώσσα μέσα σε μια JVM. Για να έχετε μια εικόνα, υπάρχουν γλώσσες που τρέχουν σε JVM, αλλά διερμηνευμένες (interpreted), όπως για παράδειγμα η JRuby και η Groovy, με αποτέλεσμα οι αντίστοιχες εφαρμογές να έχουν πιθανώς μειωμένη απόδοση κατά την εκτέλεση.

Αρκετά όμως με την ιστορία, ας περάσουμε σε λίγη δράση και άμεση επαφή με το προγραμματιστικό περιβάλλον της γλώσσας. Για τα παραδείγματα θα χρησιμοποιήσουμε την έκδοση 2.8.1, την οποία μπορεί κανείς να κατεβάσει από το site της Scala: http://scala-lang.org.

Γνωριμία με το φλοιό

Παρόλο που είπαμε ότι η Scala είναι μεταγλωττιζόμενη γλώσσα, παρέχει ένα φλοιό (κάτι σαν τους φλοιούς εντολών του Unix, όπως το bash), όπου ο χρήστης μπορεί να εισάγει εντολές και να παίρνει αμέσως αποτελέσματα. Έτσι δε χρειάζεται να γράφουμε ξεχωριστά τον κώδικα και μετά να τον μεταγλωττίζουμε. Γράφοντας λοιπόν:

$ scala
σε μια γραμμή εντολών, παίρνουμε αμέσως το αποτέλεσμα που φαίνεται στην εικόνα 2.

Μπορούμε στη συνέχεια να πειραματιστούμε με μερικά αναμενόμενα αποτελέσματα.

scala> 1 + 1 <enter>
res0: Int = 2

όπου με <enter> δηλώνουμε φυσικά ότι πατάμε το συγκεκριμένο πλήκτρο, ώστε να δοθεί στη Scala η αντίστοιχη εντολή. Τα αποτελέσματα τα παίρνουμε από τον φλοιό στην αμέσως επόμενη γραμμή. Για την προηγούμενη περίπτωση, το αποτέλεσμα είναι το res0: Int = 2. Τι σημαίνει όμως αυτό; Καταρχήν, όπως αναμένουμε, γίνεται η πρόσθεση των δύο ακεραίων, με αποτέλεσμα 2. Το αποτέλεσμα αποθηκεύεται σε μια μεταβλητή, που δημιουργείται αυτόματα, με όνομα res0. Μπορούμε να το επαληθεύσουμε, ζητώντας να υπολογιστεί η τιμή της res0:

scala> res0
res1: Int = 2

Το Int που βλέπουμε γραμμένο δίπλα στη μεταβλητή δηλώνει τον τύπο του αποτελέσματος, που στη συγκεκριμένη περίπτωση είναι ακέραιος (Integer). Όπως και στη Java, έτσι και στη Scala, οι μεταβλητές έχουν συγκεκριμένο τύπο. Ο τύπος Int είναι στη Scala το αντίστοιχο του primitive τύπου int της Java. Αν θέλαμε να συνενώσουμε δύο strings:
scala> "Hello " + " World!"
res2: java.lang.String = Hello World!

Παρατηρούμε ότι το αποτέλεσμα είναι τύπου java.lang.String. Δείτε λίγο το πρόθεμα java.lang. Η Scala επαναχρησιμοποιεί την υπάρχουσα κλάση String της Java, προκειμένου να χειρίζεται strings. Γενικά, όλες οι κλάσεις που είναι διαθέσιμες από τη Java είναι ορατές και στη Scala και μπορούμε να τις χρησιμοποιούμε κατά βούληση.

Ας πούμε τώρα ότι θα θέλαμε να ορίσουμε εμείς μια μεταβλητή με τύπο String. Ο τρόπος δήλωσης εδώ είναι κάπως διαφορετικός από ό,τι στη C και στη Java και θυμίζει λίγο Pascal:
scala> val helloWorld: String = "Hello World!"
helloWorld: String = Hello World!

Παρατηρήστε ότι χρησιμοποιούμε το val πριν το όνομα της μεταβλητής. Αυτό δηλώνει ότι η τιμή της helloWorld δεν πρόκειται να αλλάξει στη συνέχεια του προγράμματος, κατ' επιλογή του προγραμματιστή. Για την ακρίβεια μάλιστα, δεν μπορεί καν να αλλάξει:
scala> helloWorld = "It's a rainy Sunday"
<console>:6: error: reassignment to val
helloWorld = "It's a rainy Sunday"
^

Ο φλοιός μας αναφέρει το λάθος. Φυσικά, αν επιθυμούμε πραγματικές "μεταβλητές", όπως τις έχουμε συνηθίσει, πρέπει να χρησιμοποιήσουμε var αντί για val:

scala> var helloWorld = "Hello World!"
helloWorld: java.lang.String = Hello World!

scala> helloWorld = "It's another world"
helloWorld: java.lang.String = It's another world

Δείτε λίγο την παραπάνω δήλωση var, σε αντιδιαστολή με την προηγούμενη δήλωση val για τη μεταβλητή με όνομα helloWorld. Βλέπετε κάποια άλλη διαφορά; Επικεντρώστε στον τύπο... Την πρώτη φορά είπαμε στη Scala ότι ο τύπος είναι String. Τη δεύτερη φορά δεν είπαμε τίποτα! Αλλά παρόλα αυτά ο τύπος υπολογίστηκε κανονικά. Αυτό το χαρακτηριστικό, του να υπολογίζει η Scala τύπους που είναι προφανείς, μας γλιτώνει από περιττή πληκτρολόγηση, κάνοντας την προγραμματιστική καθημερινότητα πιο εύκολη.

Εδώ χρειάζεται λίγο προσοχή. Το ότι δε δηλώνουμε εμείς τύπο, δε σημαίνει ότι δεν αποδίδεται συγκεκριμένος τύπος στη μεταβλητή. Με λίγα λόγια, η Scala δεν εξομοιώνει τη συμπεριφορά π.χ. της Python, όπου εκεί οι τύποι είναι δυναμικοί και μπορούν να αλλάζουν. Απεναντίας. Απλά ο τύπος, αν είναι τόσο προφανής ώστε να μπορεί να τον "σκεφτεί" η γλώσσα, αποδίδεται από αυτή αυτόματα.

Ένα τυπικό, απλό for-loop είναι όπως το παρακάτω. Ο κώδικας διατρέχει τους ακέραιους από ένα έως δέκα και τους τυπώνει:

scala> for(i <- 1 to 10) <enter>
| println(i)
1
2
...
10
Πατώντας <enter>, ο φλοιός αντιλαμβάνεται ότι δεν έχουμε ολοκληρώσει την εντολή και μας δίνει να γράψουμε στην επόμενη γραμμή με ένα πρόθεμα

|

Tuples

Ένα πρόβλημα που αντιμετωπίζουν πολλές φορές προγραμματιστές γλωσσών όπως η Java, είναι όταν επιθυμούν να επιστρέψουν πάνω από μία τιμές από μια μέθοδο. Μια λύση είναι να φτιάχνουμε μια ειδική κλάση που να κρατά τα συγκεκριμένα αποτελέσματα που μας ενδιαφέρουν, αλλά κάτι τέτοιο δεν είναι ιδιαίτερα βολικό και μπορεί να μας γεμίσει με πολλές μικρές κλάσεις χωρίς ιδιαίτερο άλλο ρόλο. Η Scala έχει ένα τύπο, που λέγεται tuple, και μπορεί να αξιοποιηθεί τέλεια σε τέτοιες περιπτώσεις:

scala> val t = (1, 2)
t: (Int, Int) = (1,2)

scala> val tt = (1, "one")
tt: (Int, java.lang.String) = (1,one)

Όπως βλέπουμε και στα παραπάνω, απλά ομαδοποιούμε τις τιμές που μας ενδιαφέρουν ανάμεσα από παρενθέσεις και τις διαχωρίζουμε με κόμμα. Δεν υπάρχει περιορισμός στους τύπους των διαφόρων συστατικών στοιχείων και δε χρειάζεται καν να είναι ίδιοι τύποι. Επιπλέον, επειδή η γλώσσα προάγει την ομοιομορφία, μπορούμε να έχουμε και tuples μέσα σε άλλα tuples, δεδομένου ότι ένα tuple είναι ένας τύπος όπως όλοι οι άλλοι:

scala> val ttt = ((1, 2), "yep")
ttt: ((Int, Int), java.lang.String) = ((1,2),yep)

Ένα tuple, μπορεί να δηλωθεί και με ειδικό συντακτικό x -> y:
scala> val cc = "Greece" -> "Athens"
cc: (String, String) = (Greece,Athens)

Κατά σύμβαση, ο τύπος ενός tuple, δηλώνεται και εμφανίζεται ως ένα tuple των τύπων των τιμών που το συγκροτούν.

Sets

Η Scala, πέραν της δυνατότητας να αξιοποιεί οποιαδήποτε βιβλιοθήκη έχει γραφεί σε Java, διαθέτει μια πλειάδα από βιβλιοθήκες γραμμένες σε Scala, πού είναι ακόμα πιο ευέλικτες και πιο κοντά στο πνεύμα της γλώσσας. Μερικά στοιχεία μάλιστα, μας θυμίζουν scripting γλώσσες. Για παράδειγμα, για συλλογές με μοναδικά αντικείμενα (sets), η σύνταξη είναι πολύ απλή:

scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4) 

Εδώ, έχει ενδιαφέρον να παρατηρήσουμε δύο πράγματα. Πρώτον, από το πλήρες όνομα του τύπου, που εμπεριέχει το λεκτικό immutable, θα πρέπει να καταλαβαίνουμε πως το αποτέλεσμα είναι ένα set, το οποίο δεν μπορεί να μεταβληθεί ως προς τα περιεχόμενά του. Γενικά, όσον αφορά συλλογές, η Scala διαχωρίζει και υλοποιεί δύο είδη: συλλογές που μετά τη δημιουργία τους δεν μπορούν να μεταβληθούν (immutable) και συλλογές που μπορούν να μεταβληθούν (mutable), ως προς τα περιεχόμενά τους πάντα.

Δεύτερον, ο τύπος Set είναι παραμετροποιημένος με βάση τον τύπο των στοιχείων που συμμετέχουν στη συλλογή. Έχουμε έτσι ένα Set από τιμές τύπου Int, πράγμα το οποίο δηλώνεται με το Set[Int]. Η αντίστοιχη σύνταξη σε Java θα ήταν Set<Int>.

Οι συλλογές που δεν μπορούν να μεταβληθούν, έχουν ένα ενδιαφέρον χαρακτηριστικό, ότι μπορούν να χρησιμοποιηθούν με ασφάλεια σε multithreading περιβάλλοντα. Σκεφτείτε πολλά threads να προσπαθούν να αλλάξουν μια mutable συλλογή. Χωρίς τον απαραίτητο συγχρονισμό, που δεν είναι πάντα προφανές πώς θα γίνει σωστά και αποδοτικά, υπάρχει το ενδεχόμενο τα περιεχόμενα να "χαλάσουν".

Maps

Συνεχίζουμε με τα πανταχού παρόντα hash maps ή αλλιώς hash tables. Στη Scala λέγονται απλά maps.

scala> Map(1 -> "one", 2 -> "two")
res4: Map[Int, String] = Map((1,one), (2,two))

Και εδώ, η σύνταξη είναι αρκετά βολική, κοντά στα πλαίσια μιας scripting γλώσσας. Για οικονομία χώρου δεν το δείχνουμε, αλλά ο τύπος είναι ένα immutable Map, σύμφωνα με τη φιλοσοφία που εξηγήσαμε πριν. Παρατηρήστε ότι ο τύπος Map επίσης παραμετροποιείται με βάση το κλειδί και την τιμή που αντιστοιχεί σε αυτό. Εδώ μάλιστα, η Scala υπολογίζει για μας τους τύπους Int και String αντίστοιχα, με βάση τις τιμές που έχουμε δώσει. Επίσης, όταν δώσαμε την εντολή δημιουργίας του map, στην ουσία το κλειδί και την τιμή του τι δώσαμε με τη μορφή tuple, π.χ. 1 -> "one".

Lists

Η λίστα είναι από τις θεμελιώδεις δομές και η Scala στο συγκεκριμένο σημείο προάγει κάποιες βασικές ιδέες από το συναρτησιακό προγραμματισμό. Έτσι, η βασική δομή λίστας, με τύπο List, ορίζεται ως είτε η κενή λίστα, είτε ως λίστα που έχει ένα στοιχείο ως κεφαλή (head) και μια άλλη λίστα ως ουρά (tail). Η κενή λίστα δηλώνεται ως Nil:

scala> Nil
res6: scala.collection.immutable.Nil.type = List()

Μια λίστα με ένα στοιχείο, δημιουργείται από τη συνένωση του στοιχείου με την κενή λίστα:
scala> val one = 1 :: Nil
one: List[Int] = List(1)

Ό τελεστής :: είναι ο υπεύθυνος για τη συνένωση. Το παραπάνω μπορούμε να το δηλώσουμε πιο σύντομα, όπως ίσως θα υποψιάζεστε ήδη από το αποτέλεσμα που μας έδωσε ο φλοιός:

scala> val another = List(1)
another: List[Int] = List(1)

Κατ' επέκταση, μια λίστα με περισσότερα του ενός στοιχεία μπορεί να δοθεί με δύο εναλλακτικούς τρόπους:

scala> val list1 = List(1, 2, 3, 4)
list1: List[Int] = List(1, 2, 3, 4)

scala> val list2 =1 :: 2 :: 3 :: 4 :: Nil
list2: List[Int] = List(1, 2, 3, 4)

Αναζητήσεις και μετασχηματισμοί

Ας πούμε ότι από την παραπάνω λίστα list1 θέλουμε να φιλτράρουμε όλα τα στοιχεία που είναι ζυγοί αριθμοί. Ο παρακάτω κώδικας επιτυγχάνει ακριβώς αυτό:

scala> list1.filter(x => x % 2 == 0)
res10: List[Int] = List(2, 4)

Η βασική μέθοδος είναι η filter, που ορίζεται στη List, αλλά το ενδιαφέρον κομμάτι είναι η παράμετρος. Στην ουσία περνάμε ως παράμετρο μια συνάρτηση μιας μεταβλητής x, η οποία μας λέει αν το x διαιρείται ακριβώς με το 2. Πρόκειται για μια ανώνυμη συνάρτηση που δημιουργούμε κατά βούληση, με πολύ ελαφριά σύνταξη της μορφής x => f(x), όπου f(x) είναι ο ακριβής ορισμός. Στη συγκεκριμένη περίπτωση:

f(x) = x % 2 == 0

και το αποτέλεσμα είναι πάντα true ή false, δηλαδή μια τιμή τύπου Boolean. Τι κάνει όμως η μέθοδος filter; Αυτό που κάνει είναι να διατρέχει όλη τη λίστα και να καλεί για κάθε στοιχείο της την f(x). Όλα τα στοιχεία x για τα οποία το αποτέλεσμα είναι true, τα μαζεύει σε μία νέα λίστα, την οποία στο τέλος η filter επιστρέφει ως αποτέλεσμα.

Αν έχετε προγραμματίσει σε Java (C, C++), αντιλαμβάνεστε αμέσως την περιεκτικότητα του συντακτικού της Scala, αλλά και τη δύναμη των βιβλιοθηκών της.

Ας πούμε τώρα θέλουμε να πάρουμε μια νέα λίστα από τη list1, αλλά με το κάθε στοιχείο μετασχηματισμένο στον αντίθετό του ακέραιο αριθμό. Ίσως ήδη υποψιάζεστε ότι αντί να φτιάξουμε εμείς τη νέα λίστα, να τη διατρέξουμε και να μετασχηματίζουμε το κάθε στοιχείο, να μπορεί η Scala να το κάνει για μας με το λιγότερο δυνατό κώδικα. Πράγματι μπορεί και είναι ο ενδεδειγμένος τρόπος να γίνει κάτι τέτοιο:

scala> list1.map(x => -x)
res11: List[Int] = List(-1, -2, -3, -4)

Και πάλι χρησιμοποιούμε την κατάλληλη συνάρτηση μετασχηματισμού. Η μέθοδος map, που κάνει όλη τη δουλειά για μας, διατρέχει όλη τη λίστα και μετασχηματίζει, σε μια νέα λίστα, ένα-ένα τα στοιχεία της αρχικής, σύμφωνα με τη συνάρτηση:

f(x) = -x

Επειδή μάλιστα, η συνάρτηση είναι τόσο απλή στον ορισμό της, που χρησιμοποιεί τη μεταβλητή x μόνο μια φορά, μπορούμε να κάνουμε το μετασχηματισμό με ακόμα πιο σύντομο προγραμματισμό:

scala> list1.map(- _)
res13: List[Int] = List(-1, -2, -3, -4)

Εδώ, το underscore _ έχει συγκεκριμένη σημασία στο συντακτικό της γλώσσας και υποδηλώνει μια ανώνυμη μεταβλητή. Μιας και ο ορισμός της x δε χρησιμεύει παρά μόνο στον υπολογισμό του αντίθετου αριθμού, μπορούμε να τον παραλείψουμε εντελώς!

Μέθοδοι

Μια μέθοδος στη Scala ορίζεται με τη λέξη κλειδί def. Αν γνωρίζετε Python, η σύνταξη είναι παρόμοια, αλλά όχι ίδια μιας και, ανάμεσα στα άλλα, η Scala χρησιμοποιεί blocks τύπου Java, με άγκιστρα {}. Η παρακάτω μέθοδος υπολογίζει το αποτέλεσμα της διαίρεσης και το υπόλοιπο δύο ακεραίων:

scala> def divmod(a: Int, b: Int) = {
| ( a / b,
| a % b)
| }
divmod: (a: Int,b: Int)(Int, Int)

Φυσικά, χρησιμοποιούμε ένα tuple για να επιστρέψουμε τις δύο τιμές. Μια δοκιμή μας δείχνει ότι δουλεύει όπως περιμέναμε:

scala> divmod(7, 3)
res14: (Int, Int) = (2,1)

Ο φλοιός, για διευκόλυνση, μας δίνει τη δυνατότητα να γράφουμε μεθόδους όπως πριν, αλλά κανονικά μια μέθοδος πρέπει να ορίζεται στα πλαίσια μιας κλάσης. Στην ουσία, επειδή ο κώδικας τρέχει σε JVM, όπου η βασική δομική μονάδα είναι η κλάση, ο φλοιός της Scala δημιουργεί στο παρασκήνιο αυτόματα κλάσεις που περιέχουν τον κώδικά μας. Πρόκειται για αυτοματισμό που μας γλιτώνει από άσκοπη πολλές φορές πληκτρολόγηση, ιδιαίτερα όταν το μόνο που θέλουμε αρκετές φορές είναι να δοκιμάσουμε γρήγορα μια σχεδιαστική ιδέα, ένα API που σχεδιάσαμε εμείς ή κατεβάσαμε από το διαδίκτυο ή και ακόμα να κάνουμε βασικές αριθμητικές πράξεις, όπως αυτή με την οποία ξεκίνησε το άρθρο.

Λίγη εξερεύνηση στα πλαίσια του φλοιού μας αποζημιώνει. Ένας linux hacker θα μπει στον πειρασμό να πατήσει TAB περιμένοντας κάποιας μορφής συμπλήρωση, όπως στο bash και η αλήθεια είναι ότι θα αποζημιωθεί. Δοκιμάστε το μόνοι σας!

Επίσης, δίνοντας την ειδική εντολή του φλοιού :power, μπαίνουμε σε ένα "power-mode", όπου έχουμε στη διάθεσή μας τις εσωτερικές δομές του και μερικές επιπλέον εντολές. Πολλά από αυτά τα στοιχεία απαιτούν βέβαια μια καλή γνώση του πώς λειτουργεί ο compiler της Scala, αλλά η ουσία είναι ότι έχουμε στα χέρια μας ένα scripting περιβάλλον για όλες τις δουλειές και τα επίπεδα δυσκολίας στα οποία επιθυμεί κάποιος να κινηθεί.

Κλάσεις

Μια κλάση δηλώνεται με τη λέξη κλειδί class, με τον constructor να έπεται αμέσως:

scala> class Complex(val r: Double, val i: Double) {
| def add(c: Complex) = new Complex(
| r + c.r,
| i + c.i)
| override def toString() =
| "Complex(" + r + ", " + i + ")"
| }

Εδώ ορίσαμε ένα μιγαδικό αριθμό, με μία μέθοδο που υλοποιεί την πράξη της πρόσθεσης στους μιγαδικούς. Ο constructor χρειάζεται δύο αριθμούς τύπου Double, που επειδή δηλώνονται και ως val, ταυτόχρονα αποτελούν και πεδία της κλάσης. Η γνωστή σύνταξη κλάση.πεδίο χρησιμοποιείται και στη Scala για την πρόσβαση στα πεδία, όπως φαίνεται και από το c.r και c.i στον ορισμό της μεθόδου add. Παρατηρήστε ότι ορίσαμε και τη μέθοδο toString, η οποία υπάρχει σε όλα τα αντικείμενα στη JVM, ώστε να έχουμε μια πιο φιλική παρουσίαση σε μορφή συμβολοσειράς ενός στιγμιοτύπου της Complex. Η λέξη κλειδί override δηλώνει ότι επανα-ορίζουμε την προϋπάρχουσα υλοποίηση στη γονεϊκή κλάση java.lang.Object, μια κλάση που κληρονομούν όλες οι άλλες.

Μπορούμε να χρησιμοποιήσουμε την κλάση Complex ως εξής:

scala> val i = new Complex(0, 1)
i: Complex = (0.0, 1.0)
scala> val one = new Complex(1, 0)
one: Complex = (1.0, 0.0)
scala> val sum = one.add(i)
sum: Complex = (1.0, 1.0)

Στην ουσία, δημιουργήσαμε τη φανταστική μονάδα των μιγαδικών και την προσθέσαμε με την πραγματική μονάδα, κάνοντας χρήση της μεθόδου add. Η Scala μας επιτρέπει όμως να είμαστε ακόμα πιο αποδοτικοί στον προγραμματισμός μας, δημιουργώντας ορισμούς τελεστών. Έτσι, μπορούμε να ξαναγράψουμε τον ορισμό της Complex, αλλάζοντας την add με τον τελεστή +.

scala> class Complex(...)
| def +(c: Complex) = new Complex(
| ...
| }

Στη συνέχεια, η πρόσθεση μπορεί να γίνει σαν οι μιγαδικοί αριθμοί να ήταν κομμάτι της ίδιας της γλώσσας:

scala> new Complex(0, 1) + new Complex(1, 0)
res20: Complex = (1.0, 1.0)

Scripts

Λόγω της ύπαρξης του φλοιού, η Scala μας ενθαρρύνει να γράφουμε scripts (σενάρια φλοιού). Ο γράφων χρησιμοποιεί στην καθημερινή του εργασία πλέον διάφορα scala scripts που έχουν αντικαταστήσει αντίστοιχα shell scripts. Ένα scala script το εκτελούμε με την εντολή:

$ scala SCRIPTNAME

όπου SCRIPTNAME είναι το όνομα του αρχείου που περιέχει τον πηγαίο κώδικα. Κατά σύμβαση, τα αρχεία αυτά έχουν κατάληξη .scala. Στην εικόνα 3 δίνεται ένα ολοκληρωμένο Scala script, που σκοπό έχει να αλλάζει τη μεταβλητή περιβάλλοντος PATH. Το χρησιμοποιούμε ως εξής: αν η μεταβλητή PATH έχει τιμή

PATH=/bin:/usr/bin

και θέλουμε να αλλάξουμε το /bin σε /mybin, τότε θα καλέσουμε το scipt:

$ scala change-path.scala /bin /mybin

Το αποτέλεσμα θα γραφεί στο stdout και μετά μπορούμε να το αναθέσουμε σε μια άλλη μεταβλητή ή να τροποποιήσουμε κατευθείαν τη μεταβλητή PATH.

Επίλογος

Είδαμε με μια πολύ γρήγορη ματιά μερικά χαρακτηριστικά της γλώσσας Scala. Το επίσημο site της γλώσσας, http://scala-lang.org, έχει μια πληθώρα από χρήσιμες πληροφορίες και τεκμηρίωση για περαιτέρω μελέτη. Από το site μπορείτε να πληροφορηθείτε για τις λίστες email που υπάρχουν και να επιλέξετε να συμμετέχετε σε κάποιες από αυτές.

Η κοινότητα της Scala είναι πολύ ευχάριστη και δραστήρια. Μια φορά το χρόνο γίνεται ένα workshop αφιερωμένο στη γλώσσα και τον κόσμο που την υποστηρίζει. Ο γράφων είχε την ευτυχία να συμμετέχει στο πρώτο από αυτά, που έγινε την άνοιξη του 2010 στην Ελβετία και οι εντυπώσεις ήταν απίστευτα θετικές.

Αν θέλετε να βουτήξετε στον εκφραστικό αλλά και πολύ πρακτικό κόσμο της Scala, το βιβλίο του γράφοντος, σε συνεργασία με τον Α. Συρόπουλο, με τίτλο "Steps in Scala: An introduction to object-functional programming" μπορεί να σας βοηθήσει. Ξεκινά με μηδενική γνώση της γλώσσας, διατρέχει το συντακτικό και τις βιβλιοθήκες της, αντιμετωπίζοντας και αναλύοντας θέματα σχεδιασμού και υλοποίησης από τον πραγματικό κόσμο, σε διάφορα επίπεδα δυσκολίας.


«Η Scala υπολογίζει τύπους που είναι προφανείς και μας γλιτώνει από περιττή πληκτρολόγηση.»

«Οι εφαρμογές σε Scala είναι όσο περισσότερο αποδοτικές μπορεί να είναι εφαρμογές που τρέχουν στη JVM.»


Που χρησιμοποιείται η Scala;

Η Scala χρησιμοποιείται αυτή τη στιγμή από τις πιο hot εταιρείες του πλανήτη, όπως το Twitter και το Foursquare, επειδή είναι σύντομη, ασφαλής και πολύ παραγωγική όταν γράφεις κώδικα. Λόγω του ότι μεταγλωττίζεται κατευθείαν σε bytecodes, οι εφαρμογές σε Scala είναι όσο αποδοτικές μπορεί να είναι και οι εφαρμογές που τρέχουν στη JVM.

 

Ο Χρήστος ακροβατεί μεταξύ τέχνης και επιστήμης, δημιουργώντας λογισμικό.

Δώσε αστέρια!