Οι συναρτήσεις παρουσιάστηκαν νωρίτερα στην έννοια Basics, με παραδείγματα σαν κι αυτό:
squareit <- function(x) {
x * x
}
squareit(3)
#> [1] 9
# shorter form
squareit_short <- function(x) x ^ 2
Κοιτώντας πιο προσεκτικά τον ορισμό της squareit, μπορούμε να ξεχωρίσουμε διάφορα μέρη:
x.{ }.squareit.Μια συνάρτηση είναι αντικείμενο πρώτης κατηγορίας στην R, όπως ακριβώς οι αριθμοί και οι συμβολοσειρές.
Έτσι, το squareit <- function... είναι μια εκχώρηση που συντακτικά μοιάζει ακριβώς με το x <- 42.
Η πρόσβαση στα συστατικά μέρη μιας συνάρτησης είναι σπάνια στην κανονική χρήση, αλλά αρκετά εύκολη.
class(squareit)
#> [1] "function"
formals(squareit)
#> $x
body(squareit)
#> {
#> x * x
#> }
Τα ορίσματα που λαμβάνουμε με τη formals() μοιάζουν με λίστα, με τη σύνταξη $x.
Στην πραγματικότητα, ο τύπος είναι pairlist, ένας συγκεκριμένος τύπος λίστας που περιέχει ζεύγη κλειδιού-τιμής.
Το body είναι εκτελέσιμος κώδικας, με τύπο language (κάτι που μάλλον δεν χρειάζεται να σε απασχολήσει).
Η R δεν κάνει σαφή διάκριση ανάμεσα σε ορίσματα θέσης και ορίσματα με όνομα, σε αντίθεση με άλλες γλώσσες σεναρίων όπως η Python και η Julia.
Οι κλήσεις συναρτήσεων μπορούν να περνούν τιμές είτε κατά θέση είτε με όνομα. Το δεύτερο είναι χρήσιμο για σύνθετες συναρτήσεις με πολλά ορίσματα, όπου είναι δύσκολο να θυμάσαι τη σειρά τους.
f <- function(x, y) x / y
# call positionally
f(4, 2)
#> [1] 2
# call by name
f(y = 2, x = 4)
#> [1] 2
Οι προεπιλεγμένες τιμές ορισμάτων μπορούν να οριστούν στον ορισμό της συνάρτησης, αλλά πρέπει να έρχονται μετά από όλα τα ορίσματα χωρίς προεπιλογή.
Μπορούμε τότε να επιλέξουμε αν θα δεχτούμε την προεπιλογή ή θα την παρακάμψουμε.
g <- function(x, y = 2) x / y
# default y value
g(6)
#> [1] 3
# explicit y value
g(6, 3)
#> [1] 2
Για να δεχτείς έναν αυθαίρετο αριθμό επιπλέον ορισμάτων, χρησιμοποίησε ένα ... (αποσιωπητικά) στον ορισμό.
Είναι δυνατό να μετατρέψεις οποιεσδήποτε επιπλέον τιμές στην κλήση της συνάρτησης σε διάνυσμα, αλλά διάβασε παρακάτω για έναν εναλλακτικό τρόπο χρήσης αυτών των "dot args" (που σε αρκετές άλλες γλώσσες ονομάζονται "varargs").
var_f <- function(x, y, ...) {
print(c(...))
}
var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"
Προηγουμένως, είπαμε ότι οι τυπικές παράμετροι και το σώμα είναι και τα δύο συστατικά μέρη μιας συνάρτησης.
Στην πραγματικότητα, υπάρχει και ένα τρίτο συστατικό: το περιβάλλον στο οποίο ορίζεται η συνάρτηση.
Αυτό φαίνεται χαρακτηριστικά στην περίπτωση των εμφωλευμένων συναρτήσεων:
outer_func <- function(x) {
inner_func <- function(y) {
x * y
}
inner_func(3)
}
outer_func(5)
#> [1] 15
Η κλήση της συνάρτησης περνά το x = 5 στην εξωτερική συνάρτηση, και αυτή η τιμή είναι διαθέσιμη μέσα στο σώμα αυτής της συνάρτησης.
Η εσωτερική συνάρτηση είναι μέρος του σώματος της εξωτερικής συνάρτησης και έχει πρόσβαση στην τιμή του x.
Διατυπωμένο διαφορετικά, το x = 5 βρίσκεται στο περιβάλλον της εσωτερικής συνάρτησης.
Τεχνικά, αυτό είναι γνωστό ως κλειστότητα.
Το περιβάλλον είναι ιδιαίτερα σημαντικό με τα dot args, καθώς οποιεσδήποτε τιμές δίνονται με αυτόν τον τρόπο μπορούν να περάσουν σε κλήσεις συναρτήσεων μέσα στο σώμα της συνάρτησης. Η εξωτερική συνάρτηση δεν χρειάζεται να ξέρει ή να νοιάζεται τι σημαίνουν τα dot args.
f_var <- function(x, ...) {
sum(x, ...)
}
x <- c(1, 2, NA, 6)
# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA
# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9
Αυτή η τεχνική χρησιμοποιείται εκτενώς από βιβλιοθήκες του Tidyverse, όπως η stringr.
Πολλές από τις συναρτήσεις της stringr είναι ένα φιλικό προς τον χρήστη περιτύλιγμα γύρω από συναρτήσεις χαμηλού επιπέδου της stringi και της βασικής R.
Τα επιπλέον ορίσματα που δίνονται στις συναρτήσεις str_*() απλώς περνούν σε αυτές τις συναρτήσεις χαμηλότερου επιπέδου.
Μια εξωτερική συνάρτηση μπορεί να ορίσει μια εσωτερική συνάρτηση (είτε ονομαστική είτε ανώνυμη, όπως περιγράφεται παρακάτω) και να τη χρησιμοποιήσει ως τιμή επιστροφής.
Η συνάρτηση που επιστρέφεται θα περιλαμβάνει το περιβάλλον στο οποίο ορίστηκε.
times_y <- function(x) {
# anonymous function - see next section
\(y) x * y
}
f <- times_y(3)
class(f)
#> [1] "function"
f(4)
#> [1] 12
Όταν ορίζουμε μια συνάρτηση, συνήθως δεσμεύουμε το αντικείμενο συνάρτησης που προκύπτει σε μια μεταβλητή:
x <- 2
# function definition captures x from the environment
doubleit_short <- function(y) x * y
Αυτό κάνει εύκολη τη χρήση της συνάρτησης αργότερα στο σενάριο, αλλά μια τέτοια δέσμευση δεν είναι απαραίτητη. Μια σύντομη συνάρτηση για μία μόνο χρήση μπορεί να είναι χρήσιμη στο άμεσο πλαίσιο. Χωρίς δέσμευση ονόματος, ονομάζεται ανώνυμη συνάρτηση.
Η χρήση ανώνυμων συναρτήσεων είναι τόσο συχνή που (από την έκδοση R 4.1.0) υπάρχει μια συντομευμένη σύνταξη για τον ορισμό τους: αντικατέστησε τη λέξη function με μια ανάποδη κάθετο \.
Ένα παράδειγμα αυτού χρησιμοποιήθηκε στην προηγούμενη ενότητα, για τις τιμές επιστροφής.
Η R επιτρέπει εκχώρηση σε μεμονωμένα στοιχεία ενός διανύσματος. Αν περάσουμε ένα διάνυσμα ως όρισμα συνάρτησης και το τροποποιήσουμε μέσα στο σώμα της συνάρτησης πριν το επιστρέψουμε, παίρνουμε ένα τροποποιημένο διάνυσμα.
Τι συνέβη όμως στο αρχικό διάνυσμα;
f <- function(vec) {
vec[1] <- 42
vec
}
vals <- c(1, 3, 4)
# f() returns a modified vector
f(vals)
#> [1] 42 3 4
# the original vector is unchanged.
vals
#> [1] 1 3 4
Η R είναι μια γλώσσα σχεδιασμένη για την επιστήμη των δεδομένων. Η συλλογή αυτών των δεδομένων μπορεί να κοστίσει πολύ σε χρόνο, κόπο και ενδεχομένως ένα τσουχτερό χρηματικό ποσό: είναι σημαντικό να μην τα αλλοιώσεις!
Η γενική πολιτική (με λίγες εξαιρέσεις) είναι αντιγραφή κατά την τροποποίηση. Αν ένα αντικείμενο (όπως ένα διάνυσμα) αλλάξει με τρόπο που θα μπορούσε να προκαλέσει προβλήματα αργότερα, η R επιστρέφει ένα τροποποιημένο αντίγραφο και αφήνει το αρχικό άθικτο.
Η αντιγραφή μεγάλων δομών δεδομένων μπορεί να είναι υπολογιστικά δαπανηρή, αλλά αυτό είναι γενικά το μικρότερο κακό όταν η εναλλακτική είναι η αλλοίωση των δεδομένων.
Πρόσεχε τις πράξεις που οδηγούν σε επανειλημμένη αντιγραφή των ίδιων δεδομένων.
Παραφράζοντας τον (συγγραφέα του Tidyverse) Hadley Wickham:
Οι βρόχοι δεν είναι από τη φύση τους αργοί, αλλά κάνουν επικίνδυνα εύκολο να συμπεριλάβεις αργές πράξεις μέσα στον βρόχο.
Ο διανυσματισμός ή οι συναρτήσεις ανώτερης τάξης μπορούν να σε βοηθήσουν να προστατευτείς από αυτού του είδους τον δολοφόνο της απόδοσης.