Προγραμματισμός

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

Το Java 8 εισήγαγε προγραμματιστές Java σε λειτουργικό προγραμματισμό με εκφράσεις lambda. Αυτή η έκδοση Java ενημέρωσε αποτελεσματικά τους προγραμματιστές ότι δεν αρκεί πλέον να σκεφτόμαστε τον προγραμματισμό Java μόνο από την επιτακτική, αντικειμενοστραφή προοπτική. Ένας προγραμματιστής Java πρέπει επίσης να μπορεί να σκέφτεται και να κωδικοποιεί χρησιμοποιώντας το δηλωτικό λειτουργικό παράδειγμα.

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

Ο λειτουργικός προγραμματισμός αυξάνεται

Το Ινστιτούτο Ηλεκτρολόγων Μηχανικών και Ηλεκτρονικών Μηχανικών (IEEE) συμπεριέλαβε λειτουργικές γλώσσες προγραμματισμού στις 25 κορυφαίες γλώσσες προγραμματισμού για το 2018 και το Google Trends κατατάσσει τον λειτουργικό προγραμματισμό ως πιο δημοφιλές από τον αντικειμενοστραφή προγραμματισμό.

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

λήψη Λήψη του κώδικα Λήψη του πηγαίου κώδικα για παράδειγμα εφαρμογές σε αυτό το σεμινάριο. Δημιουργήθηκε από τον Jeff Friesen για το JavaWorld.

Τι είναι ο λειτουργικός προγραμματισμός;

Οι υπολογιστές εφαρμόζουν συνήθως την αρχιτεκτονική Von Neumann, η οποία είναι μια ευρέως χρησιμοποιούμενη αρχιτεκτονική υπολογιστών με βάση μια περιγραφή του 1945 από τον μαθηματικό και φυσικό John von Neumann (και άλλους). Αυτή η αρχιτεκτονική είναι προκατειλημμένη επιτακτικός προγραμματισμός, το οποίο είναι ένα παράδειγμα προγραμματισμού που χρησιμοποιεί δηλώσεις για να αλλάξει την κατάσταση ενός προγράμματος. Οι C, C ++ και Java είναι όλες επιτακτικές γλώσσες προγραμματισμού.

Το 1977, ο διακεκριμένος επιστήμονας υπολογιστών John Backus (γνωστός για το έργο του στο FORTRAN), έδωσε μια διάλεξη με τίτλο "Μπορεί ο προγραμματισμός να απελευθερωθεί από το στυλ von Neumann;". Ο Backus ισχυρίστηκε ότι η αρχιτεκτονική του Von Neumann και οι συναφείς υποχρεωτικές γλώσσες της είναι βασικά ελαττωματικές και παρουσίασαν μια γλώσσα προγραμματισμού λειτουργικού επιπέδου (FP) ως λύση.

Αποσαφήνιση του Backus

Επειδή η διάλεξη Backus παρουσιάστηκε πριν από αρκετές δεκαετίες, μερικές από τις ιδέες της μπορεί να είναι δύσκολο να κατανοηθούν. Ο Blogger Tomasz Jaskuła προσθέτει σαφήνεια και υποσημειώσεις στην ανάρτηση ιστολογίου του από τον Ιανουάριο του 2018.

Έννοιες και ορολογία λειτουργικού προγραμματισμού

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

Οι λειτουργικές γλώσσες προγραμματισμού είναι δηλωτικός, που σημαίνει ότι η λογική ενός υπολογισμού εκφράζεται χωρίς να περιγράφεται η ροή ελέγχου. Στον δηλωτικό προγραμματισμό, δεν υπάρχουν δηλώσεις. Αντ 'αυτού, οι προγραμματιστές χρησιμοποιούν εκφράσεις για να πουν στον υπολογιστή τι πρέπει να γίνει, αλλά όχι πώς να ολοκληρώσει την εργασία. Εάν είστε εξοικειωμένοι με SQL ή κανονικές εκφράσεις, τότε έχετε κάποια εμπειρία με το δηλωτικό στυλ. Και οι δύο χρησιμοποιούν εκφράσεις για να περιγράψουν τι πρέπει να γίνει, αντί να χρησιμοποιούν δηλώσεις για να περιγράψουν πώς να το κάνουν.

ΕΝΑ υπολογισμός στο λειτουργικό προγραμματισμό περιγράφεται από συναρτήσεις που αξιολογούνται σε περιβάλλοντα έκφρασης. Αυτές οι συναρτήσεις δεν είναι οι ίδιες με τις συναρτήσεις που χρησιμοποιούνται στον επιτακτικό προγραμματισμό, όπως μια μέθοδος Java που επιστρέφει μια τιμή. Αντ 'αυτού, α λειτουργικός προγραμματισμός Η συνάρτηση είναι σαν μια μαθηματική συνάρτηση, η οποία παράγει μια έξοδο που συνήθως εξαρτάται μόνο από τα επιχειρήματά της. Κάθε φορά που καλείται μια λειτουργική λειτουργία προγραμματισμού με τα ίδια επιχειρήματα, επιτυγχάνεται το ίδιο αποτέλεσμα. Οι λειτουργίες στον λειτουργικό προγραμματισμό λέγεται ότι παρουσιάζουν διαφάνεια αναφοράς. Αυτό σημαίνει ότι θα μπορούσατε να αντικαταστήσετε μια κλήση συνάρτησης με την προκύπτουσα τιμή χωρίς να αλλάξετε το νόημα του υπολογισμού.

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

Παρενέργειες στον επιτακτικό και λειτουργικό προγραμματισμό

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

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

Μια άλλη κοινή ανεπιθύμητη ενέργεια συμβαίνει κατά την τροποποίηση της συμπεριφοράς μιας επιτακτικής συνάρτησης βάσει μιας εξαιρούμενης εξαίρεσης, η οποία είναι μια παρατηρήσιμη αλληλεπίδραση με τον καλούντα. Για περισσότερες πληροφορίες, ανατρέξτε στη συζήτηση Stack Overflow, "Γιατί η αύξηση μιας εξαίρεσης είναι παρενέργεια;"

Μια τρίτη κοινή ανεπιθύμητη ενέργεια εμφανίζεται όταν μια λειτουργία εισόδου / εξόδου εισάγει κείμενο που δεν μπορεί να είναι μη αναγνωσμένο ή εξάγει κείμενο που δεν μπορεί να γραφτεί. Δείτε τη συζήτηση Stack Exchange "Πώς μπορεί το IO να προκαλέσει παρενέργειες στον λειτουργικό προγραμματισμό;" για να μάθετε περισσότερα σχετικά με αυτήν την παρενέργεια.

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

Προέλευση (και δημιουργός) λειτουργικού προγραμματισμού

Ο λειτουργικός προγραμματισμός προήλθε από το lambda calculus, το οποίο εισήχθη από την Alonzo Church. Μια άλλη προέλευση είναι η συνδυαστική λογική, η οποία εισήχθη από τον Moses Schönfinkel και στη συνέχεια αναπτύχθηκε από τον Haskell Curry.

Αντικειμενοστραφής έναντι λειτουργικού προγραμματισμού

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

Λίστα 1. Employees.java

εισαγωγή java.util.ArrayList; εισαγωγή java.util.List; δημόσιος υπάλληλος κατηγορίας {στατική τάξη Υπάλληλος {ιδιωτικό όνομα συμβολοσειράς; ιδιωτική ηλικία Υπάλληλος (όνομα συμβολοσειράς, int age) {this.name = name; this.age = ηλικία; } int getAge () {ηλικία επιστροφής; } @Override public String toString () {return name + ":" + age; }} public static void main (String [] args) {Λίστα υπαλλήλων = νέο ArrayList (); jobs.add (νέος υπάλληλος ("John Doe", 63)); jobs.add (νέος υπάλληλος ("Sally Smith", 29)); jobs.add (νέος υπάλληλος ("Bob Jone", 36)); jobs.add (νέος υπάλληλος ("Margaret Foster", 53)) printEm Employee1 (εργαζόμενοι, 50); System.out.println (); printEm Employee2 (εργαζόμενοι, 50); } public static void printEm Employee1 (Λίστα υπαλλήλων, int age) {για (Υπάλληλος: εργαζόμενοι) εάν (emp.getAge () <age) System.out.println (emp); } public static void printEm Employee2 (Λίστα υπαλλήλων, int age) {karyawan.stream () .filter (emp -> emp.age System.out.println (emp)); }}

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

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

ο printEm Employee2 () Η μέθοδος αποκαλύπτει τη δηλωτική, προσανατολισμένη στην έκφραση προσέγγιση, στην περίπτωση αυτή που εφαρμόζεται με το Streams API. Αντί να καθορίζει επιτακτικά τον τρόπο εκτύπωσης των εργαζομένων (βήμα προς βήμα), η έκφραση καθορίζει το επιθυμητό αποτέλεσμα και αφήνει τις λεπτομέρειες για το πώς να το κάνετε στην Java. Σκέφτομαι φίλτρο() ως το λειτουργικό ισοδύναμο ενός αν δήλωση, και για κάθε() ως λειτουργικά ισοδύναμο με το Για δήλωση.

Μπορείτε να μεταγλωττίσετε την Καταχώριση 1 ως εξής:

javac Employees.java

Χρησιμοποιήστε την ακόλουθη εντολή για να εκτελέσετε την εφαρμογή που προκύπτει:

Υπάλληλοι java

Η έξοδος θα πρέπει να μοιάζει με αυτό:

Sally Smith: 29 Bob Jone: 36 Sally Smith: 29 Bob Jone: 36

Παραδείγματα λειτουργικού προγραμματισμού

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

Η λίστα 2 παρουσιάζει τον πηγαίο κώδικα στο RunScript, μια εφαρμογή Java που χρησιμοποιεί το Java Scripting API για να διευκολύνει την εκτέλεση κώδικα JavaScript. RunScript θα είναι το βασικό πρόγραμμα για όλα τα προσεχή παραδείγματα.

Λίστα 2. RunScript.java

εισαγωγή java.io.FileReader; εισαγωγή java.io.IOException; εισαγωγή javax.script.ScriptEngine; εισαγωγή javax.script.ScriptEngineManager; εισαγωγή javax.script.ScriptException; εισαγωγή στατικού java.lang.System. *; δημόσια τάξη RunScript {public static void main (String [] args) {if (args.length! = 1) {err.println ("use: java RunScript script"); ΕΠΙΣΤΡΟΦΗ; } ScriptEngineManager manager = νέο ScriptEngineManager (); ScriptEngine engine = manager.getEngineByName ("nashorn"); δοκιμάστε το {engine.eval (νέο FileReader (args [0])); } catch (ScriptException se) {err.println (se.getMessage ()); } catch (IOException πρόσβαση) {err.println (ζη.getMessage ()); }}}

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

Υποθέτοντας την παρουσία αυτού του επιχειρήματος, κύριος() δημιουργεί το javax.script.ScriptEngineManager τάξη. ScriptEngineManager είναι το σημείο εισόδου στο Java Scripting API.

Στη συνέχεια, το ScriptEngineManager αντικείμενο ScriptEngine getEngineByName (συμβολοσειρά shortName) Η μέθοδος καλείται για να αποκτήσει μια μηχανή σεναρίων που αντιστοιχεί στην επιθυμητή μικρό όνομα αξία. Το Java 10 υποστηρίζει τη μηχανή σεναρίων Nashorn, η οποία αποκτάται περνώντας "nashorn" προς την getEngineByName (). Η κλάση του επιστρεφόμενου αντικειμένου εφαρμόζει το javax.script.ScriptEngine διεπαφή.

Σενάριο δηλώνει αρκετά eval () μέθοδοι για την αξιολόγηση ενός σεναρίου. κύριος() επικαλείται το Αντικείμενο eval (Αναγνώστης αναγνώστη) μέθοδος για να διαβάσετε το σενάριο από το java.io.FileReader επιχείρημα αντικειμένου και (υποθέτοντας ότι java.io.IOException δεν ρίχνεται) στη συνέχεια αξιολογήστε το σενάριο. Αυτή η μέθοδος επιστρέφει οποιαδήποτε τιμή επιστροφής σεναρίου, την οποία αγνοώ. Επίσης, αυτή η μέθοδος ρίχνει javax.script.ScriptException όταν παρουσιάζεται σφάλμα στο σενάριο.

Συγκεντρώστε την καταχώριση 2 ως εξής:

javac RunScript.java

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

Λειτουργικός προγραμματισμός με καθαρές λειτουργίες

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

Μπορεί μια καθαρή λειτουργία να εκτελεί I / O;

Εάν το I / O είναι παρενέργεια, μπορεί μια καθαρή λειτουργία να εκτελέσει I / O; Η απάντηση είναι ναι. Η Haskell χρησιμοποιεί monads για την αντιμετώπιση αυτού του προβλήματος. Ανατρέξτε στην ενότητα "Pure Functions and I / O" για περισσότερα σχετικά με τις καθαρές λειτουργίες και το I / O.

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

Το JavaScript στην καταχώριση 3 έρχεται σε αντίθεση με ένα ακαθόριστο calculbonus () λειτουργούν με καθαρό calculbonus2 () λειτουργία.

Λίστα 3. Σύγκριση καθαρών και μη καθαρών συναρτήσεων (script1.js)

// ακάθαρτος υπολογισμός μπόνους var όριο = 100; συνάρτηση calculbonus (numSales) {return (numSales> limit); 0.10 * numSales: 0} print (calculbonus (174)) // καθαρή συνάρτηση υπολογισμού μπόνους calculbonus2 (numSales) {return (numSales> 100); 0.10 * numSales: 0} print (calculbonus2 (174))

calculbonus () είναι ακάθαρτο επειδή έχει πρόσβαση στο εξωτερικό όριο μεταβλητός. Σε αντίθεση, calculbonus2 () είναι καθαρή επειδή πληροί και τις δύο απαιτήσεις για καθαρότητα. Τρέξιμο script1.js ως εξής:

java RunScript script1.js

Εδώ είναι η έξοδος που πρέπει να παρατηρήσετε:

17.400000000000002 17.400000000000002

Υποθέτω calculbonus2 () επαναπροσδιορίστηκε επιστροφή calculbonus (numSales). Θα calculbonus2 () ακόμα να είσαι αγνός; Η απάντηση είναι όχι: όταν μια καθαρή συνάρτηση επικαλείται μια ακάθαρτη συνάρτηση, η «καθαρή συνάρτηση» γίνεται ακάθαρτη.

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

Περισσότερα για τις καθαρές λειτουργίες

Δεν πρέπει να είναι καθαρές όλες οι λειτουργικές λειτουργίες προγραμματισμού. Όπως εξηγεί ο Λειτουργικός προγραμματισμός: Οι καθαρές λειτουργίες, είναι πιθανό (και μερικές φορές επιθυμητό) να "διαχωρίσετε τον καθαρό, λειτουργικό, βασισμένο στην αξία πυρήνα της εφαρμογής σας από ένα εξωτερικό, επιτακτικό κέλυφος."

Λειτουργικός προγραμματισμός με λειτουργίες υψηλότερης τάξης

ΕΝΑ συνάρτηση υψηλότερης τάξης είναι μια μαθηματική συνάρτηση που λαμβάνει συναρτήσεις ως ορίσματα, επιστρέφει μια συνάρτηση στον καλούντα ή σε αμφότερα. Ένα παράδειγμα είναι ο διαφορικός τελεστής του λογισμού, d / dx, που επιστρέφει το παράγωγο της συνάρτησης φά.

Οι λειτουργίες πρώτης κατηγορίας είναι πολίτες πρώτης κατηγορίας

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

Το JavaScript στην καταχώριση 4 δείχνει τη μετάδοση ανώνυμων συναρτήσεων σύγκρισης σε μια λειτουργία ταξινόμησης πρώτης κατηγορίας.

Λίστα 4. Διαβίβαση ανώνυμων συναρτήσεων σύγκρισης (script2.js)

είδος λειτουργίας (a, cmp) {για (var pass = 0; pass  πέρασμα; i--) εάν (cmp (a [i], a [pass]) <0) {var temp = a [i] a [i] = a [pass] a [pass] = temp}} var a = [ 22, 91, 3, 45, 64, 67, -1] ταξινόμηση (a, συνάρτηση (i, j) {return i - j;}) a.forEach (function (entry) {print (entry)}) εκτύπωση ( '\ n') ταξινόμηση (a, function (i, j) {return j - i;}) a.forEach (function (entry) {print (entry)}) print ('\ n') a = ["X "," E "," Q "," A "," P "] ταξινόμηση (a, function (i, j) {return i  ι; }) a.forEach (λειτουργία (καταχώριση) {εκτύπωση (καταχώριση)}) εκτύπωση ('\ n') ταξινόμηση (a, συνάρτηση (i, j) {return i> j? -1: i <j;}) a .forEach (λειτουργία (καταχώριση) {εκτύπωση (καταχώριση)})

Σε αυτό το παράδειγμα, το αρχικό είδος() Η κλήση λαμβάνει έναν πίνακα ως πρώτο όρισμα, ακολουθούμενο από μια ανώνυμη λειτουργία σύγκρισης. Όταν καλείται, εκτελείται η ανώνυμη λειτουργία σύγκρισης επιστροφή i - j; για να επιτύχετε ένα ανερχόμενο είδος. Με αντιστροφή Εγώ και ι, η δεύτερη λειτουργία σύγκρισης επιτυγχάνει φθίνουσα σειρά. Το τρίτο και το τέταρτο είδος() Οι κλήσεις λαμβάνουν ανώνυμες λειτουργίες σύγκρισης που είναι ελαφρώς διαφορετικές προκειμένου να συγκρίνουν σωστά τις τιμές συμβολοσειράς.

Εκτελέστε το script2.js παράδειγμα ως εξής:

java RunScript script2.js

Εδώ είναι η αναμενόμενη έξοδος:

-1 3 22 45 64 67 91 91 67 64 45 22 3 -1 A E P Q X X Q P E A

Φιλτράρισμα και χάρτης

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

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

Το JavaScript υποστηρίζει τη λειτουργία φιλτραρίσματος και χαρτογράφησης μέσω του φίλτρο() και χάρτης() συναρτήσεις υψηλότερης τάξης. Η λίστα 5 δείχνει αυτές τις λειτουργίες για το φιλτράρισμα περίεργων αριθμών και τη χαρτογράφηση αριθμών στους κύβους τους.

Λίστα 5. Φιλτράρισμα και χαρτογράφηση (script3.js)

print ([1, 2, 3, 4, 5, 6] .filter (function (num) {return num% 2 == 0})) print ('\ n') εκτύπωση ([3, 13, 22]. χάρτης (συνάρτηση (αριθμός) {return num * 3}))

Εκτελέστε το script3.js παράδειγμα ως εξής:

java RunScript script3.js

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

2,4,6 9,39,66

Περιορίζω

Μια άλλη κοινή λειτουργία υψηλότερης τάξης είναι περιορίζω, το οποίο είναι πιο γνωστό ως πτυχή. Αυτή η λειτουργία μειώνει μια λίστα σε μία μόνο τιμή.

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

Λίστα 6. Μείωση μιας σειράς αριθμών σε έναν μόνο αριθμό (script4.js)

αριθμοί var = [22, 30, 43] εκτύπωση (number.reduce (συνάρτηση (acc, curval) {return acc + curval}) / angka.length)

Εκτελέστε το σενάριο του List 6 (σε script4.js) ως εξής:

java RunScript script4.js

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

31.666666666666668

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

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

Λειτουργικός προγραμματισμός με τεμπέλης αξιολόγηση

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

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

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

Το Java's Streams API αξιοποιεί την τεμπέληνη αξιολόγηση. Οι ενδιάμεσες λειτουργίες μιας ροής (π.χ. φίλτρο()) είναι πάντα τεμπέλης δεν κάνουν τίποτα μέχρι μια τερματική λειτουργία (π.χ. για κάθε()εκτελείται.

Αν και η τεμπέλης αξιολόγηση είναι ένα σημαντικό μέρος των λειτουργικών γλωσσών, ακόμη και πολλές επιτακτικές γλώσσες παρέχουν ενσωματωμένη υποστήριξη για ορισμένες μορφές τεμπελιάς. Για παράδειγμα, οι περισσότερες γλώσσες προγραμματισμού υποστηρίζουν αξιολόγηση βραχυκυκλώματος στο πλαίσιο των τελεστών Boolean AND και OR. Αυτοί οι τελεστές είναι τεμπέλης, αρνούνται να αξιολογήσουν τους δεξιούς τελεστές τους όταν ο αριστερός τελεστής είναι ψευδής (AND) ή true (OR).

Η λίστα 7 είναι ένα παράδειγμα τεμπέλης αξιολόγησης σε ένα σενάριο JavaScript.

Λίστα 7. Οκνηρή αξιολόγηση σε JavaScript (script5.js)

var a = false && mahalFunction ("1") var b = true && mahalFunction ("2") var c = false || mahalFunction ("3") var d = true || mahalFunction ("4") function mahalFunction (id) {print ("mahalFunction () ονομάζεται με" + id)}

Εκτελέστε τον κωδικό στο script5.js ως εξής:

java RunScript script5.js

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

mahalFunction () που ονομάζεται με 2 mahalFunction () που ονομάζεται με 3

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

Επειδή η τεμπέλης αξιολόγηση δεν λειτουργεί με παρενέργειες (όπως κώδικας που παράγει εξαιρέσεις και I / O), οι επιτακτικές γλώσσες χρησιμοποιούν κυρίως πρόθυμη αξιολόγηση (επίσης γνωστός ως αυστηρή αξιολόγηση), όπου μια έκφραση αξιολογείται μόλις δεσμευτεί σε μια μεταβλητή.

Περισσότερα για τεμπέλη αξιολόγηση και απομνημόνευση

Μια αναζήτηση στο Google θα αποκαλύψει πολλές χρήσιμες συζητήσεις τεμπέλης αξιολόγησης με ή χωρίς απομνημόνευση. Ένα παράδειγμα είναι "Βελτιστοποίηση του JavaScript με λειτουργικό προγραμματισμό."

Λειτουργικός προγραμματισμός με κλείσιμο

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

Κατασκευή κλεισίματος

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

Για να διευκρινιστεί αυτή η ιδέα, η Λίστα 8 παρουσιάζει ένα σενάριο JavaScript που εισάγει ένα απλό κλείσιμο. Το σενάριο βασίζεται στο παράδειγμα που παρουσιάζεται εδώ.

Λίστα 8. Ένα απλό κλείσιμο (script6.js)

function add (x) {function partialAdd (y) {return y + x} return partialAdd} var add10 = add (10) var add20 = add (20) print (add10 (5)) εκτύπωση (add20 (5))

Η λίστα 8 ορίζει μια συνάρτηση πρώτης κατηγορίας που ονομάζεται Προσθήκη() με μια παράμετρο Χ και μια ένθετη συνάρτηση partialAdd (). Η ένθετη συνάρτηση partialAdd () έχει πρόσβαση σε Χ επειδή Χ είναι μέσα Προσθήκη()λεξικό πεδίο. Λειτουργία Προσθήκη() επιστρέφει ένα κλείσιμο που περιέχει μια αναφορά στο partialAdd () και ένα αντίγραφο του περιβάλλοντος γύρω Προσθήκη(), στο οποίο Χ έχει την τιμή που έχει εκχωρηθεί σε μια συγκεκριμένη επίκληση του Προσθήκη().

Επειδή Προσθήκη() επιστρέφει μια τιμή τύπου συνάρτησης, μεταβλητών προσθήκη10 και προσθήκη20 έχουν επίσης τύπο λειτουργίας. ο προσθήκη 10 (5) επιστρέφει η επίκληση 15 επειδή η επίκληση εκχωρεί 5 στην παράμετρο γ στην κλήση προς partialAdd (), χρησιμοποιώντας το αποθηκευμένο περιβάλλον για partialAdd () όπου Χ είναι 10. ο προσθήκη20 (5) επιστρέφει η επίκληση 25 γιατί, αν και εκχωρεί επίσης 5 προς την γ στην κλήση προς partialAdd (), τώρα χρησιμοποιεί ένα άλλο αποθηκευμένο περιβάλλον για partialAdd () όπου Χ είναι 20. Έτσι, ενώ προσθήκη 10 () και προσθήκη20 () χρησιμοποιήστε την ίδια λειτουργία partialAdd (), τα σχετικά περιβάλλοντα διαφέρουν και η επίκληση των κλεισίματος θα δεσμευτεί Χ σε δύο διαφορετικές τιμές στις δύο επίκληση, αξιολογώντας τη συνάρτηση σε δύο διαφορετικά αποτελέσματα.

Εκτελέστε το σενάριο του List 8 (σε script6.js) ως εξής:

java RunScript script6.js

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

15 25

Λειτουργικός προγραμματισμός με currying

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

Η λίστα 9 παρουσιάζει ένα σενάριο JavaScript που δείχνει την κάρι.

Καταχώριση 9. Currying σε JavaScript (script7.js)

συνάρτηση πολλαπλασιασμού (x, y) {return x * y} function curried_multiply (x) {return function (y) {return x * y}} print (multiply (6, 7)) print (curried_multiply (6) (7)) var mul_by_4 = curried_multiply (4) εκτύπωση (mul_by_4 (2))

Το σενάριο παρουσιάζει ένα μη διψασμένο δύο επιχειρήματα πολλαπλασιάζω() λειτουργία, ακολουθούμενη από μια πρώτης τάξεως curried_multiply () συνάρτηση που λαμβάνει όρισμα multiplicand Χ και επιστρέφει ένα κλείσιμο που περιέχει μια αναφορά σε μια ανώνυμη συνάρτηση (που λαμβάνει πολλαπλασιαστικό όρισμα γ) και ένα αντίγραφο του περιβάλλοντος γύρω curried_multiply (), στο οποίο Χ έχει την τιμή που του αποδίδεται σε επίκληση του curried_multiply ().

Το υπόλοιπο σενάριο επικαλείται πρώτα πολλαπλασιάζω() με δύο επιχειρήματα και εκτυπώνει το αποτέλεσμα. Στη συνέχεια επικαλείται curried_multiply () με δύο τρόπους:

  • curried_multiply (6) (7) αποτελέσματα σε curried_multiply (6) εκτελεί πρώτα. Το επιστρεφόμενο κλείσιμο εκτελεί την ανώνυμη συνάρτηση με το κλείσιμο αποθηκευμένο Χ αξία 6 πολλαπλασιάζεται με 7.
  • var mul_by_4 = curried_multiply (4) εκτελεί curried_multiply (4) και αναθέτει το κλείσιμο σε mul_by_4. mul_by_4 (2) εκτελεί την ανώνυμη συνάρτηση με το κλείσιμο 4 τιμή και το όρισμα που πέρασε 2.

Εκτελέστε το σενάριο του List 9 (σε script7.js) ως εξής:

java RunScript script7.js

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

42 42 8

Γιατί να χρησιμοποιήσετε το κάρυ;

Στην ανάρτηση στο blog του "Γιατί το κάρυ βοηθά", ο Χιου Τζάκσον παρατηρεί ότι "τα μικρά κομμάτια μπορούν να διαμορφωθούν και να επαναχρησιμοποιηθούν με ευκολία, χωρίς ακαταστασία." Quora's "Ποια είναι τα πλεονεκτήματα του λειτουργικού προγραμματισμού;" περιγράφει το κάρι ως "μια φτηνή μορφή έγχυσης εξάρτησης", που διευκολύνει τη διαδικασία χαρτογράφησης / φιλτραρίσματος / αναδίπλωσης (και λειτουργίες υψηλότερης τάξης γενικά). Αυτό το Q&A σημειώνει επίσης ότι το κάρισμα "μας βοηθά να δημιουργήσουμε αφηρημένες λειτουργίες".

Συμπερασματικά

Σε αυτό το σεμινάριο μάθατε μερικά βασικά του λειτουργικού προγραμματισμού. Έχουμε χρησιμοποιήσει παραδείγματα σε JavaScript για να μελετήσουμε πέντε βασικές τεχνικές λειτουργικού προγραμματισμού, τις οποίες θα διερευνήσουμε περαιτέρω χρησιμοποιώντας τον κώδικα Java στο Μέρος 2. Εκτός από την περιοδεία των δυνατοτήτων λειτουργικού προγραμματισμού Java 8, το δεύτερο μισό αυτού του σεμιναρίου θα σας βοηθήσει να ξεκινήσετε σκεφτείτε λειτουργικά, μετατρέποντας ένα παράδειγμα αντικειμενοστρεφούς κώδικα Java σε λειτουργικό ισοδύναμο.

Μάθετε περισσότερα σχετικά με τον λειτουργικό προγραμματισμό

Βρήκα το βιβλίο Εισαγωγή στον Λειτουργικό Προγραμματισμό (Richard Bird and Philip Wadler, Prentice Hall International Series in Computing Science, 1992) χρήσιμο στην εκμάθηση των βασικών στοιχείων του λειτουργικού προγραμματισμού.

Αυτή η ιστορία, "Λειτουργικός προγραμματισμός για προγραμματιστές Java, Μέρος 1" δημοσιεύθηκε αρχικά από την JavaWorld.