Λειτουργικός προγραμματισμός για προγραμματιστές Android - Μέρος 1

Τον τελευταίο καιρό, ξοδεύω πολύ χρόνο μαθαίνοντας το Elixir, μια φοβερή λειτουργική γλώσσα προγραμματισμού που είναι φιλική προς τους αρχάριους.

Αυτό με έκανε να σκεφτώ: γιατί να μην χρησιμοποιήσω μερικές από τις έννοιες και τις τεχνικές από τον λειτουργικό κόσμο στον προγραμματισμό Android;

Όταν οι περισσότεροι άνθρωποι ακούνε τον όρο Λειτουργικός Προγραμματισμός, σκέφτονται αναρτήσεις για Ειδήσεις Hacker για Monads, Λειτουργίες υψηλότερης παραγγελίας και τύπους αφηρημένων δεδομένων Φαίνεται να είναι ένα μυστικιστικό σύμπαν πολύ μακριά από τις προσπάθειες του καθημερινού προγραμματιστή, προοριζόμενο μόνο για ισχυρότερους χάκερ που κατάγονται από τη σφαίρα του Númenor.

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

Καλώς ορίσατε στον Λειτουργικό προγραμματισμό (FP) για προγραμματιστές Android. Σε αυτήν τη σειρά, θα μάθουμε τις βασικές αρχές του FP και πώς μπορούμε να τα χρησιμοποιήσουμε σε μια καλή παλιά Java και σε ένα νέο φοβερό Kotlin. Η ιδέα είναι να διατηρήσουμε τις έννοιες στηριγμένες στην πρακτικότητα και να αποφύγουμε όσο το δυνατόν περισσότερη ακαδημαϊκή ορολογία.

Το FP είναι ένα τεράστιο θέμα. Θα μάθουμε μόνο τις έννοιες και τις τεχνικές που είναι χρήσιμες για τη σύνταξη κώδικα Android. Μπορεί να επισκεφθούμε μερικές έννοιες που δεν μπορούμε να χρησιμοποιήσουμε άμεσα για λόγους πληρότητας, αλλά θα προσπαθήσω να κρατήσω το υλικό όσο το δυνατόν πιο σχετικό.

Ετοιμος? Πάμε.

Τι είναι ο Λειτουργικός προγραμματισμός και γιατί πρέπει να τον χρησιμοποιήσω;

Καλή ερώτηση. Ο όρος Λειτουργικός προγραμματισμός είναι μια ομπρέλα για μια σειρά από έννοιες προγραμματισμού, τις οποίες το μονοπάτι δεν κάνει δίκαιο. Στον πυρήνα του, είναι ένα στυλ προγραμματισμού που αντιμετωπίζει τα προγράμματα ως αξιολόγηση των μαθηματικών συναρτήσεων και αποφεύγει μεταβλητές καταστάσεις και παρενέργειες (θα μιλήσουμε για αυτά σύντομα).

Στον πυρήνα του, η FP τονίζει:

  • Δηλωτικός κώδικας - Οι προγραμματιστές πρέπει να ανησυχούν για το τι και να αφήσουν τον μεταγλωττιστή και τον χρόνο εκτέλεσης να ανησυχούν για το πώς .
  • Explicitness - Ο κώδικας πρέπει να είναι όσο το δυνατόν πιο προφανής. Συγκεκριμένα, παρενέργειεςνα απομονωθούν για να αποφευχθούν εκπλήξεις. Η ροή δεδομένων και ο χειρισμός σφαλμάτων ορίζονται ρητά και αποφεύγονται δομές όπως οι δηλώσεις GOTO και οι εξαιρέσεις , καθώς μπορούν να θέσουν την εφαρμογή σας σε απρόσμενες καταστάσεις.
  • Concurrency - Ο περισσότερος λειτουργικός κώδικας είναι ταυτόχρονος από προεπιλογή λόγω μιας έννοιας που είναι γνωστή ως λειτουργική καθαρότητα . Η γενική συμφωνία φαίνεται να είναι ότι αυτό το χαρακτηριστικό προκαλεί την αύξηση της δημοτικότητας του λειτουργικού προγραμματισμού, καθώς οι πυρήνες της CPU δεν γίνονται ταχύτεροι κάθε χρόνο όπως στο παρελθόν (βλ. Νόμο του Moore) και πρέπει να κάνουμε τα προγράμματα μας πιο ταυτόχρονα για να επωφεληθούμε πολυπύρηνων αρχιτεκτονικών.
  • Λειτουργίες Υψηλότερης Τάξης - Οι συναρτήσεις είναι μέλη πρώτης τάξης όπως όλα τα πρωτότυπα γλωσσών. Μπορείτε να περάσετε συναρτήσεις όπως θα κάνατε μια συμβολοσειρά ή ένα int.
  • Αμετάβλητο - Οι μεταβλητές δεν πρέπει να τροποποιηθούν μόλις αρχικοποιηθούν. Μόλις δημιουργηθεί ένα πράγμα, είναι αυτό το πράγμα για πάντα. Εάν θέλετε να αλλάξει, δημιουργείτε ένα νέο πράγμα. Αυτή είναι μια άλλη πτυχή της ρητής μαρτυρίας και της αποφυγής παρενεργειών. Εάν γνωρίζετε ότι ένα πράγμα δεν μπορεί να αλλάξει, έχετε πολύ μεγαλύτερη εμπιστοσύνη για την κατάστασή του όταν το χρησιμοποιείτε.

Δηλωτικός, ρητός και ταυτόχρονος κώδικας που είναι πιο εύκολο να σκεφτεί κανείς και έχει σχεδιαστεί για να αποφεύγει εκπλήξεις; Ελπίζω να έχω αυξήσει το ενδιαφέρον σας.

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

Καθαρές λειτουργίες

Μια λειτουργία είναι καθαρή εάν η έξοδος της εξαρτάται μόνο από την είσοδό της και δεν έχει παρενέργειες (θα μιλήσουμε για τις παρενέργειες λίγο μετά από αυτό). Ας δούμε ένα παράδειγμα, έτσι;

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

Ιάβα

int add(int x) { int y = readNumFromFile(); return x + y;}

Κότλιν

fun add(x: Int): Int { val y: Int = readNumFromFile() return x + y}

Η έξοδος αυτής της συνάρτησης δεν εξαρτάται αποκλειστικά από την είσοδό της. Ανάλογα με το τι επιστρέφει το readNumFromFile () , μπορεί να έχει διαφορετικές εξόδους για την ίδια τιμή του x . Αυτή η λειτουργία λέγεται ότι είναι ακάθαρτη .

Ας το μετατρέψουμε σε καθαρή λειτουργία.

Ιάβα

int add(int x, int y) { return x + y;}

Κότλιν

fun add(x: Int, y: Int): Int { return x + y}

Τώρα η έξοδος της συνάρτησης εξαρτάται μόνο από τις εισόδους της. Για δεδομένο x και y, η συνάρτηση θα επιστρέφει πάντα την ίδια έξοδο. Αυτή η λειτουργία λέγεται τώρα καθαρή . Οι μαθηματικές συναρτήσεις λειτουργούν επίσης με τον ίδιο τρόπο. Η έξοδος μαθηματικών συναρτήσεων εξαρτάται μόνο από τις εισόδους της - Αυτός είναι ο λόγος για τον οποίο ο λειτουργικός προγραμματισμός είναι πολύ πιο κοντά στα μαθηματικά από το συνηθισμένο στυλ προγραμματισμού που έχουμε συνηθίσει.

PS Μια κενή είσοδος εξακολουθεί να είναι είσοδος. Εάν μια συνάρτηση δεν λαμβάνει εισόδους και επιστρέφει την ίδια σταθερά κάθε φορά, είναι ακόμα καθαρή.

PPS Η ιδιότητα του να επιστρέφετε πάντα την ίδια έξοδο για μια δεδομένη είσοδο είναι επίσης γνωστή ως διαφάνεια αναφοράς και μπορεί να την δείτε να χρησιμοποιείται όταν μιλάτε για καθαρές λειτουργίες.

Παρενέργειες

Ας εξερευνήσουμε αυτήν την ιδέα με το ίδιο παράδειγμα λειτουργίας προσθήκης. Θα τροποποιήσουμε τη λειτουργία προσθήκης για να γράψουμε επίσης το αποτέλεσμα σε ένα αρχείο.

Ιάβα

int add(int x, int y) { int result = x + y; writeResultToFile(result); return result;}

Κότλιν

fun add(x: Int, y: Int): Int { val result = x + y writeResultToFile(result) return result}

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

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

Λειτουργίες που έχουν παρενέργειες αποφεύγονται στο FP επειδή δεν είναι πλέον καθαρές και εξαρτώνται από το ιστορικό πλαίσιο . Το περιεχόμενο του κώδικα δεν είναι αυτόνομο. Αυτό τους καθιστά πολύ πιο δύσκολο να το σκεφτούν.

Ας υποθέσουμε ότι γράφετε ένα κομμάτι κώδικα που εξαρτάται από μια προσωρινή μνήμη. Τώρα η έξοδος του κωδικού σας εξαρτάται από το αν κάποιος έγραψε στην κρυφή μνήμη, τι γράφτηκε σε αυτήν, πότε γράφτηκε, εάν τα δεδομένα είναι έγκυρα κ.λπ. Δεν μπορείτε να καταλάβετε τι κάνει το πρόγραμμά σας, εκτός εάν καταλαβαίνετε όλες τις πιθανές καταστάσεις από την προσωρινή μνήμη από την οποία εξαρτάται. Εάν το επεκτείνετε για να συμπεριλάβετε όλα τα άλλα στοιχεία στα οποία εξαρτάται η εφαρμογή σας - δίκτυο, βάση δεδομένων, αρχεία, είσοδο χρήστη και ούτω καθεξής, γίνεται πολύ δύσκολο να γνωρίζετε τι ακριβώς συμβαίνει και να τα τοποθετήσετε ταυτόχρονα στο μυαλό σας.

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

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

Παραγγελία

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

Ας πούμε ότι έχουμε μια λειτουργία που καλεί 3 καθαρές λειτουργίες εσωτερικά:

Ιάβα

void doThings() { doThing1(); doThing2(); doThing3();}

Κότλιν

fun doThings() { doThing1() doThing2() doThing3()}

Γνωρίζουμε με βεβαιότητα ότι αυτές οι λειτουργίες δεν εξαρτώνται η μία από την άλλη (καθώς η έξοδος της μιας δεν είναι η είσοδος της άλλης) και γνωρίζουμε επίσης ότι δεν θα αλλάξουν τίποτα στο σύστημα (αφού είναι καθαρά). Αυτό καθιστά τη σειρά με την οποία εκτελούνται εντελώς εναλλάξιμες.

Η σειρά εκτέλεσης μπορεί να ανακατασκευαστεί και να βελτιστοποιηθεί για ανεξάρτητες καθαρές λειτουργίες. Σημειώστε ότι εάν η είσοδος του doThing2 () ήταν το αποτέλεσμα του doThing1 (), τότε αυτά θα έπρεπε να εκτελεστούν με τη σειρά, αλλά το doThing3 () θα μπορούσε να παραγγελθεί ξανά για εκτέλεση πριν από το doThing1 ().

Τι μας προσφέρει αυτή η παραγγελία; Ταυτόχρονα, αυτό είναι! Μπορούμε να εκτελέσουμε αυτές τις λειτουργίες σε 3 ξεχωριστούς πυρήνες CPU χωρίς να ανησυχούμε για να βιδώσουμε τίποτα!

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

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

Περίληψη

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

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

Διαβάστε παρακάτω

Λειτουργικός προγραμματισμός για προγραμματιστές Android - Μέρος 2

Εάν δεν έχετε διαβάσει το μέρος 1, διαβάστε το εδώ: medium.com

Εάν σας άρεσε αυτό, κάντε κλικ στο? παρακάτω. Παρατηρώ ο καθένας και είμαι ευγνώμων για όλα αυτά.

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