Η Julia ενθαρρύνει τους προγραμματιστές να βάζουν όσο το δυνατόν περισσότερο κώδικα μέσα σε συναρτήσεις που μπορούν να μεταγλωττιστούν JIT, και η δημιουργία πολλών μικρών συναρτήσεων είναι, εκ σχεδιασμού, αποδοτική.
Αυτό τείνει να αφήνει πολλές μικρές, απλές συναρτήσεις, οι οποίες πρέπει να συνδυαστούν για να εκτελέσουν μη τετριμμένες εργασίες.
Μια προφανής προσέγγιση είναι να εμφωλεύεις κλήσεις συναρτήσεων. Το παρακάτω παράδειγμα είναι πολύ τεχνητό, αλλά δείχνει το σημείο.
julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
Το μειονέκτημα αυτής της προσέγγισης είναι ότι η αναγνωσιμότητα πέφτει γρήγορα όσο το βάθος της εμφώλευσης αυξάνεται.
Χρειαζόμαστε μια απλούστερη και πιο ευέλικτη προσέγγιση.
Αυτή είναι η τεχνική που αγαπούν οι μαθηματικοί, και η Julia αντιγράφει τη μαθηματική σύνταξη.
Ένας αυθαίρετος αριθμός συναρτήσεων μπορεί να composed μαζί με τελεστές ∘ (που εισάγονται ως \circ και μετά tab).
Το αποτέλεσμα μπορεί να χρησιμοποιηθεί ως μία μόνο συνάρτηση.
julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])
Δύο σημεία για να προσέξεις:
Μια εναλλακτική θα μπορούσε να θεωρηθεί ως η προσέγγιση των προγραμματιστών, παρά των μαθηματικών.
Η τεχνική των Pipelines χρησιμοποιείται εδώ και καιρό σε σενάρια φλοιού Unix, και πιο πρόσφατα έγινε δημοφιλής σε mainstream γλώσσες προγραμματισμού (η F# μερικές φορές πιστώνεται ότι πρωτοστάτησε στην υιοθέτησή τους).
Η βασική ιδέα είναι να ξεκινήσεις με κάποια δεδομένα, και μετά να τα διοχετεύσεις μέσα από μια ακολουθία συναρτήσεων για να πάρεις το αποτέλεσμα.
Ο τελεστής διοχέτευσης είναι |> (όπως στη F# και σε πρόσφατες εκδόσεις της R), αν και η Julia έχει επίσης μια έκδοση broadcasting .|>.
julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
Η εκτέλεση είναι τώρα αυστηρά από αριστερά προς τα δεξιά, με την έξοδο κάθε συνάρτησης να ρέει προς την κατεύθυνση του βέλους για να γίνει είσοδος για την επόμενη συνάρτηση.
Δεν είναι σύμπτωση ότι οι συναρτήσεις που χρησιμοποιήθηκαν για να δείξουν τη σύνθεση και τη διοχέτευση παίρνουν όλες ένα μόνο όρισμα.
Κάποιες καθαρά συναρτησιακές γλώσσες διοχετεύουν το πρώτο όρισμα σε μια συνάρτηση, αλλά επιτρέπουν να συμπεριληφθούν και άλλα.
Αντιθέτως, η Julia αναμένει μόνο ονόματα συναρτήσεων (ή κάτι ισοδύναμο) σε μια διοχέτευση, χωρίς επιπλέον ορίσματα.
Υπάρχουν σημαντικοί τεχνικοί λόγοι για αυτό (σχετίζονται με το γεγονός ότι το currying δεν είναι στάνταρ μέρος του σχεδιασμού της γλώσσας).
Οι πολλοί άνθρωποι που δεν καταλαβαίνουν το currying θα πρέπει απλώς να δεχτούν ότι αυτός ο περιορισμός δεν είναι μια απρόσεκτη παράλειψη, και δεν είναι πιθανό να αλλάξει σε μελλοντικές εκδόσεις της Julia.
Χρειαζόμαστε συναρτήσεις με ένα όρισμα που κάνουν ό,τι χρειάζεται. Ευτυχώς, ο ορισμός νέων συναρτήσεων στη Julia είναι εύκολος.
Πιο απλά, θα μπορούσαμε να χρησιμοποιήσουμε μια anonymous function.
Για παράδειγμα, αν έχουμε μια μόνο συμβολοσειρά εισόδου και θέλουμε να τη χωρίσουμε με βάση τις κάτω παύλες:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Αυτό το διάνυσμα θα μπορούσε τότε να διοχετευτεί σε άλλες συναρτήσεις, όπως πριν.
Η περικλείωση της ανώνυμης συνάρτησης σε παρενθέσεις είναι προαιρετική σε αυτή την περίπτωση, αλλά γενικότερα είναι ένας χρήσιμος τρόπος για να μειωθεί η ασάφεια.
Ομοίως, θα μπορούσαμε να δημιουργήσουμε μια ονομασμένη συνάρτηση, νωρίτερα στο πρόγραμμα, και να την επαναχρησιμοποιήσουμε όπως χρειάζεται.
Οι Closures είναι πέρα από το πεδίο αυτής της έννοιας, αλλά όποιος τις γνωρίζει από άλλες γλώσσες θα αναγνωρίσει ότι προσφέρουν έναν πιο ευέλικτο τρόπο για να δημιουργείς συναρτήσεις με ένα όρισμα.
julia> function makesplit(sep)
fs(str) = split(str, sep)
fs
end
makesplit (generic function with 1 method)
julia> f_us = makesplit('_')
(::var"#fs#32"{Char}) (generic function with 1 method)
julia> "my_test_strings" |> f_us
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
# alternatively:
julia> "my_test_strings" |> makesplit('_')
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Μόλις οριστεί η makesplit(), μπορεί να χρησιμοποιηθεί για να δουλέψεις με οποιονδήποτε διαχωριστικό.
Σημείωσε ότι το makesplit('_') είναι μια κλήση συνάρτησης που αξιολογείται σε μια άλλη συνάρτηση, η οποία με τη σειρά της δέχεται είσοδο από τη διοχέτευση.
Αν αυτό φαίνεται μπερδεμένο, είναι φυσιολογικό στην αρχή (αλλά γίνεται πιο ξεκάθαρο με την εξάσκηση).
Υπήρξε μεγάλη συζήτηση για να γίνουν οι διοχετεύσεις πιο ευέλικτες στη βασική Julia, αλλά οι διάφορες προτάσεις είναι αμοιβαία ασύμβατες και δεν έχει επιτευχθεί συμφωνία.
Στο μεταξύ, οι χρήστες έχουν ακολουθήσει τη συνήθη προσέγγιση να δημιουργούν διάφορα εγκαταστάσιμα πακέτα που καλύπτουν συγκεκριμένες ανάγκες. Κανένα δε θα λειτουργήσει μέσα στο Exercism, αλλά ρίξε μια ματιά σε αυτά αν ενδιαφέρεσαι: