Μάθετε τη Σκάλα από 0–60: Τα βασικά

Το Scala είναι μια γλώσσα προγραμματισμού υψηλού επιπέδου γενικής χρήσης που προσφέρει ισορροπία μεταξύ της ανάπτυξης λειτουργικών και αντικειμενοστρεφών προγραμμάτων.

Τι είναι ο λειτουργικός προγραμματισμός; Με απλά λόγια, οι λειτουργίες είναι οι πολίτες πρώτης κατηγορίας στον λειτουργικό προγραμματισμό. Προκειμένου να επεκταθούμε σε ένα βασικό σύνολο λειτουργιών ενός προγράμματος, έχουμε την τάση να γράφουμε επιπρόσθετα μαθήματα που επεκτείνονται σε συγκεκριμένες οδηγίες / διεπαφές. Στον λειτουργικό προγραμματισμό, οι λειτουργίες μας βοηθούν να επιτύχουμε το ίδιο.

Θα χρησιμοποιήσουμε το Scala REPL για όλες τις εξηγήσεις. Είναι ένα πολύ εύχρηστο και ενημερωτικό εργαλείο για την εκμάθηση της Scala. Καταγράφει χαριτωμένα μικρά μηνύματα σχετικά με τον τρόπο ερμηνείας και εκτέλεσης του κώδικα.

Ας ξεκινήσουμε πρώτα με τα βασικά.

1. Μεταβλητές

Μπορούμε να ορίσουμε αμετάβλητες μεταβλητές χρησιμοποιώντας val:

scala> val name = "King"name: String = King

Οι μεταβλητές μεταβλητές μπορούν να οριστούν και να τροποποιηθούν χρησιμοποιώντας var:

scala> var name = "King"name: String = King
scala> name = "Arthur"name: String = Arthur

Χρησιμοποιούμε defγια να εκχωρήσουμε μια ετικέτα σε μια αμετάβλητη τιμή της οποίας η αξιολόγηση αναβάλλεται για αργότερα. Σημαίνει ότι η τιμή της ετικέτας εκτιμάται τεμπέλης κάθε φορά κατά τη χρήση.

scala> var name = "King"name: String = King
scala> def alias = namealias: String
scala> aliasres2: String = King

Παρατηρήσατε κάτι ενδιαφέρον;

Κατά τον ορισμό alias, δεν ανατέθηκε καμία τιμή, alias: Stringδεδομένου ότι συνδέεται τεμπέλης, όταν την επικαλούμεθα. Τι θα συνέβαινε αν αλλάξουμε την αξία του name;

scala> aliasres5: String = King
scala> name = "Arthur, King Arthur"name: String = Arthur, King Arthur
scala> aliasres6: String = Arthur, King Arthur

2. Ροή ελέγχου

Χρησιμοποιούμε δηλώσεις ροής ελέγχου για να εκφράσουμε τη λογική των αποφάσεών μας.

Μπορείτε να γράψετε μια if-elseδήλωση όπως παρακάτω:

if(name.contains("Arthur")) { print("Entombed sword")} else { print("You're not entitled to this sword")}

Ή, μπορείτε να χρησιμοποιήσετε while:

var attempts = 0while (attempts < 3) { drawSword() attempts += 1}

3. Συλλογές

Η Scala διακρίνει ρητά μεταξύ αμετάβλητων και μεταβλητών συλλογών - απευθείας από το ίδιο το χώρο ονομάτων πακέτων ( scala.collection.immutableή scala.collection.mutable).

Σε αντίθεση με τις αμετάβλητες συλλογές, οι μεταβλητές συλλογές μπορούν να ενημερωθούν ή να επεκταθούν στη θέση τους. Αυτό μας επιτρέπει να αλλάζουμε, να προσθέτουμε ή να αφαιρούμε στοιχεία ως παρενέργεια.

Ωστόσο, η εκτέλεση λειτουργιών προσθήκης, κατάργησης ή ενημέρωσης σε αμετάβλητες συλλογές επιστρέφει μια νέα συλλογή.

Οι αμετάβλητες συλλογές εισάγονται πάντα αυτόματα μέσω του scala._ (το οποίο περιέχει επίσης ψευδώνυμο για scala.collection.immutable.List).

Ωστόσο, για να χρησιμοποιήσετε μεταβλητές συλλογές, πρέπει να εισαγάγετε ρητά scala.collection.mutable.List.

Στο πνεύμα του λειτουργικού προγραμματισμού, θα βασίσουμε κυρίως τα παραδείγματα μας σε αμετάβλητες πτυχές της γλώσσας, με μικρές παρακάμψεις στην μεταβλητή πλευρά.

Λίστα

Μπορούμε να δημιουργήσουμε μια λίστα με διάφορους τρόπους:

scala> val names = List("Arthur", "Uther", "Mordred", "Vortigern")
names: List[String] = List(Arthur, Uther, Mordred, Vortigern)

Μια άλλη εύχρηστη προσέγγιση είναι να ορίσετε μια λίστα χρησιμοποιώντας τον μειονεκτήτη ::. Αυτό ενώνει ένα στοιχείο κεφαλής με την υπόλοιπη ουρά μιας λίστας.

scala> val name = "Arthur" :: "Uther" :: "Mordred" :: "Vortigern" :: Nil
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

Που ισοδυναμεί με:

scala> val name = "Arthur" :: ("Uther" :: ("Mordred" :: ("Vortigern" :: Nil)))
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

Μπορούμε να έχουμε πρόσβαση στα στοιχεία της λίστας απευθείας από το ευρετήριό τους. Θυμηθείτε ότι η Scala χρησιμοποιεί ευρετήριο μηδενικής βάσης:

scala> name(2)
res7: String = Mordred

Μερικές κοινές βοηθητικές μέθοδοι περιλαμβάνουν:

list.head, που επιστρέφει το πρώτο στοιχείο:

scala> name.head
res8: String = Arthur

list.tail, η οποία επιστρέφει την ουρά μιας λίστας (η οποία περιλαμβάνει τα πάντα εκτός από το κεφάλι):

scala> name.tail
res9: List[String] = List(Uther, Mordred, Vortigern)

Σειρά

Setμας επιτρέπει να δημιουργήσουμε μια μη επαναλαμβανόμενη ομάδα οντοτήτων. Listδεν εξαλείφει τα διπλά από προεπιλογή.

scala> val nameswithDuplicates = List("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
nameswithDuplicates: List[String] = List(Arthur, Uther, Mordred, Vortigern, Arthur, Uther)

Εδώ, το «Arthur» επαναλαμβάνεται δύο φορές και το «Uther».

Ας δημιουργήσουμε ένα σύνολο με τα ίδια ονόματα. Παρατηρήστε πώς εξαιρεί τα αντίγραφα.

scala> val uniqueNames = Set("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
uniqueNames: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

Μπορούμε να ελέγξουμε την ύπαρξη συγκεκριμένου στοιχείου στο σύνολο χρησιμοποιώντας contains():

scala> uniqueNames.contains("Vortigern")res0: Boolean = true

Μπορούμε να προσθέσουμε στοιχεία σε ένα σύνολο χρησιμοποιώντας τη μέθοδο + (η οποία απαιτεί varargsδηλαδή ορίσματα μεταβλητού μήκους)

scala> uniqueNames + ("Igraine", "Elsa", "Guenevere")res0: scala.collection.immutable.Set[String] = Set(Arthur, Elsa, Vortigern, Guenevere, Mordred, Igraine, Uther)

Παρομοίως, μπορούμε να αφαιρέσουμε στοιχεία χρησιμοποιώντας τη -μέθοδο

scala> uniqueNames - "Elsa"
res1: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

Χάρτης

Mapείναι μια επαναλαμβανόμενη συλλογή που περιέχει αντιστοιχίσεις από keyστοιχεία σε αντίστοιχα valueστοιχεία, η οποία μπορεί να δημιουργηθεί ως:

scala> val kingSpouses = Map( | "King Uther" -> "Igraine", | "Vortigern" -> "Elsa", | "King Arthur" -> "Guenevere" | )
kingSpouses: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere)

Οι τιμές για ένα συγκεκριμένο κλειδί στο χάρτη είναι προσβάσιμες ως:

scala> kingSpouses("Vortigern")res0: String = Elsa

Μπορούμε να προσθέσουμε μια καταχώρηση στο χάρτη χρησιμοποιώντας τη +μέθοδο:

scala> kingSpouses + ("Launcelot" -> "Elaine")res0: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Elaine)

Για να τροποποιήσετε μια υπάρχουσα αντιστοίχιση, προσθέτουμε απλώς ξανά την ενημερωμένη τιμή-κλειδί:

scala> kingSpouses + ("Launcelot" -> "Guenevere")res1: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Guenevere)

Σημειώστε ότι επειδή η συλλογή είναι αμετάβλητη, κάθε λειτουργία επεξεργασίας επιστρέφει μια νέα συλλογή ( res0, res1) με τις αλλαγές που εφαρμόζονται. Η αρχική συλλογή kingSpousesπαραμένει αμετάβλητη.

4. Λειτουργικοί συνδυαστές

Τώρα που έχουμε μάθει πώς να ομαδοποιούμε ένα σύνολο οντοτήτων, ας δούμε πώς μπορούμε να χρησιμοποιήσουμε λειτουργικούς συνδυασμούς για να δημιουργήσουμε σημαντικούς μετασχηματισμούς σε τέτοιες συλλογές.

Με τις απλές λέξεις του John Hughes:

Ένας συνδυασμός είναι μια συνάρτηση που δημιουργεί τμήματα προγράμματος από θραύσματα προγράμματος.

An in-depth look at how combinators work is outside of this article’s scope. But, we’ll try to touch upon a high-level understanding of the concept anyhow.

Let’s take an example.

Suppose we want to find names of all queens using the kingSpouses collection map that we created.

We’d want to do something along the lines of examining each entry in the map. If the key has the name of a king, then we’re interested in the name of it’s spouse (i.e. queen).

We shall use the filter combinator on map, which has a signature like:

collection.filter( /* a filter condition method which returns true on matching map entries */)

Overall we shall perform the following steps to find queens:

  • Find the (key, value) pairs with kings’ names as keys.
  • Extract the values (names of queen) only for such tuples.

The filter is a function which, when given a (key, value), returns true / false.

  1. Find the map entries pertaining to kings.

Let’s define our filtering predicate function. Since key_value is a tuple of (key, value), we extract the key using ._1 (and guess what ._2 returns?)

scala> def isKingly(key_value: (String, String)): Boolean = key_value._1.toLowerCase.contains("king")
isKingly: (key_value: (String, String))Boolean

Now we shall use the filter function defined above to filter kingly entries.

scala> val kingsAndQueens = kingSpouses.filter(isKingly)
kingsAndQueens: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, King Arthur -> Guenevere)

2. Extract the names of respective queens from the filtered tuples.

scala> kingsAndQueens.values
res10: Iterable[String] = MapLike.DefaultValuesIterable(Igraine, Guenevere)

Let’s print out the names of queens using the foreach combinator:

scala> kingsAndQueens.values.foreach(println)IgraineGuenevere

Some other useful combinators are foreach, filter, zip, partition, find.

We shall re-visit some of these after having learnt how to define functions and passing functions as arguments to other functions in higher-order functions.

Let’s recap on what we’ve learned:

  • Different ways of defining variables
  • Various control-flow statements
  • Μερικά βασικά στοιχεία για διάφορες συλλογές
  • Επισκόπηση της χρήσης λειτουργικών συνδυασμών σε συλλογές

Ελπίζω να βρείτε αυτό το άρθρο χρήσιμο. Είναι το πρώτο σε μια σειρά άρθρων που θα ακολουθήσει για την εκμάθηση της Scala.

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

Μη διστάσετε να με ενημερώσετε σχετικά με τα σχόλιά σας και τις προτάσεις σας σχετικά με τον τρόπο βελτίωσης του περιεχομένου. Μέχρι τότε, ❤ κωδικοποίηση.