În care mă confrunt modul în care citesc codul în diferite limbi și sfârșesc dorind ca R să aibă o caracteristică pe care nu o face.
Acesta este un pic de gândire, deoarece consider un cod-vă rugăm să nu-l luați ca o critică a oricărei alegeri de proiectare; Echipa Tidyverse a scris mărimi mai mult cod pe care îl am și au considerat cu siguranță abordarea lor mai mult decât o voi face. Cred că este util să ne contestăm propriile presupuneri și să săpătăm modul în care reacționăm la codul de lectură.
Postarea pe blog care descrie cele mai recente actualizări ale pachetului Tidyverse {Scales} demonstrează perfect utilizarea noii funcționalități, dar pentru că exemplele sunt scrise în afara codului de complot, o caracteristică mi -a rămas în special …
label_glue("The {x} penguin")(c("Gentoo", "Chinstrap", "Adelie"))
# The Gentoo penguin
# The Chinstrap penguin
# The Adelie penguin
Aici, label_glue este o funcție care ia un șir {lipici} ca argument și returnează o funcție „etichetare”. Că Funcția este apoi trecută vectorul speciilor de pinguin, care este utilizat în șirul {lipici} pentru a produce ieșirea.
📝
Nota
Pentru cei care vin la această postare dintr-un fundal Python, {Glue} este răspunsul lui R la farde F și este utilizat în același mod exact pentru cazuri simple:
## R:
name <- "Jonathan"
glue::glue("My name is {name}")
# My name is Jonathan
## Python:
>>> name="Jonathan"
>>> f"My name is {name}"
# 'My name is Jonathan'
Nu se întâmplă nimic cu magia cu label_glue()() Apel – Funcțiile sunt aplicate la argumente – dar este întotdeauna util să interogăm surpriza atunci când citiți un cod.
Scrisul de expunere a unui exemplu ar putea fi un pic mai clar. O versiune simplificată a
label_glue S -ar putea să arate așa
tmp_label_glue <- function(pattern = "{x}") {
function(x) {
glue::glue_data(list(x = x), pattern)
}
}
Acest lucru returnează o funcție care ia un argument, așa că dacă o evaluăm, obținem
tmp_label_glue("The {x} penguin")
# function(x) {
# glue::glue_data(list(x = x), pattern)
# }
#
Aceasta are beneficiul că putem stoca acest rezultat ca o nouă funcție numită
penguin_label <- tmp_label_glue("The {x} penguin")
penguin_label
# function(x) {
# glue::glue_data(list(x = x), pattern)
# }
#
#
penguin_label(c("Gentoo", "Chinstrap", "Adelie"))
# The Gentoo penguin
# The Chinstrap penguin
# The Adelie penguin
Acest lucru este versatil, deoarece șirurile diferite {lipici} pot produce funcții diferite – este un generator de funcții. Asta e îngrijit dacă tu doresc diferite funcții, dar dacă lucrați doar cu acel model, poate părea ciudat să -l numiți în linie fără a -l numi, ca exemplu anterior
label_glue("The {x} penguin")(c("Gentoo", "Chinstrap", "Adelie"))
Ea arată ca Ar trebui să putem avea toate aceste argumente în aceeași funcție
label_glue("The {x} penguin", c("Gentoo", "Chinstrap", "Adelie"))
dar în afară de faptul că label_glue nu ia etichetele ca un argument, care nu returnează o funcție și locul în care se va folosi acest lucru
ia o funcție ca argument.
Deci, de ce funcțiile de la {scale} iau funcții ca argumente? Motivul ar părea că acest lucru le permite să funcționeze Lazilly – Nu cunoaștem neapărat valorile pe care dorim să le trecem la funcția generată pe site -ul de apel; Poate că acestea sunt calculate ca parte a procesului de complotare.
De asemenea, nu vrem să ne extragem aceste etichete și să calculăm pe ele; Este convenabil să lăsați scale_* Funcție faceți asta pentru noi, dacă oferim doar o funcție pe care o poate folosi atunci când timpul este corect.
Dar ce este a trecut la acea funcție generată? Asta depinde de locul în care este folosit … dacă l -am folosit în scale_y_discrete atunci s -ar putea să arate așa
library(ggplot2) library(palmerpenguins) p <- ggplot(penguins(complete.cases(penguins), )) + aes(bill_length_mm, species) + geom_point() p + scale_y_discrete(labels = penguin_label)
De când labels argumentul ia o funcție și penguin_label este o funcție creată mai sus.
Aș putea să scriu echivalent asta ca
p + scale_y_discrete(labels = label_glue("The {x} penguin"))
și nu aveți nevoie de variabila funcțională „temporară”.
Deci, ce este trecut aici? Acest lucru este un pic greu de săpat din sursă, dar s -ar putea aștepta în mod rezonabil că, la un moment dat, funcția furnizată va fi apelată cu etichetele disponibile ca argument.
Am suspiciunea că utilizarea „externă” a acestei funcții, ca
label_glue("The {x} penguin")(c("Gentoo", "Chinstrap", "Adelie"))
se ciocnește cu înțelegerea mea (mult mai recentă) a lui Haskell și cu modul în care funcționează aplicația parțială. În Haskell, toate Funcțiile iau exact 1 argument, chiar dacă par a fi mai mult. Această funcție
ghci> do_thing x y z = x + y + z
arată ca Este nevoie de 3 argumente și acesta arată ca îl puteți folosi în acest fel
ghci> do_thing 2 3 4 9
dar Într -adevărfiecare „strat” de argumente este o funcție cu 1 argument, adică un echivalent onest ar fi
do_thing <- function(x) {
function(y) {
function(z) {
x + y + z
}
}
}
do_thing(2)(3)(4)
# (1) 9
Ceea ce este important aici este că putem „elimina” unele dintre straturi și obținem înapoi o funcție care ia argumentele rămase
do_thing(2)(3)
# function(z) {
# x + y + z
# }
#
#
partial <- do_thing(2)(3)
partial(4)
# (1) 9
În Haskell, asta arată așa
ghci> partial = do_thing 2 3 ghci> partial 4 9
Solicitarea semnăturii tipului acestei funcții arată
ghci> :type do_thing do_thing :: Num a => a -> a -> a -> a
Deci este o funcție care are o anumită valoare de tip a (ceea ce trebuie să fie un Num
Pentru că folosim + pentru adăugare; acest lucru este dedus de compilator) și atunci avem
a -> a -> a -> a
Acest lucru poate fi citit ca „o funcție care ia 3 valori ale unui tip a și returnează 1 valoare de același tip ”, dar echivalent (literal; aceasta este doar zahăr sintactic) îl putem scrie ca
a -> (a -> (a -> a))
care este „ia o valoare de tip a și returnează o funcție care ia o valoare de tip acare în sine returnează o funcție care ia o valoare de tip a și returnează o valoare de tip a”. Cu un pic de artă ASCII …
a -> (a -> (a -> a)) | | | | | | |_z__| | |_y________| |_x______________|
Dacă solicităm semnătura de tip când unii din Argumentele sunt furnizate
ghci> :type do_thing 2 3 do_thing 2 3 :: Num a => a -> a
Vedem că acum este o funcție a unei singure variabile (a -> a)
Având în vedere acest lucru, funcțiile de etichetare arată ca un candidat excelent pentru funcții parțial aplicate! Dacă am fi avut
label_glue(pattern, labels)
apoi
label_glue(pattern)
ar fi o funcție „în așteptare” pentru un labels argument. Nu este același lucru cu ceea ce avem? Aproape, dar nu chiar. label_glue nu ia o labels
argument, returnează o funcție care le va folosi, astfel încât lipsa labels
Argumentul nu este un semnal pentru acest lucru. label_glue(pattern) încă returnează o funcție, dar acest lucru nu este evident, mai ales atunci când este utilizat în linie
scale_y_discrete(labels = label_glue("The {x} penguin"))
Când citesc codul R de genul acesta, văd parantezele la sfârșitul label_glue
și citiți -o ca „aceasta este o invocare a funcției; valoarea de returnare va fi folosită aici”. Este corect, dar în acest caz, valoarea de returnare este o altă funcție. Nu există nimic care să spună „asta va returna o funcție”. Nu există nicio convenție în R pentru a semnala acest lucru (și a fi tipat dinamic, tot ce se poate face este să citești documentația), dar ne -am putea imagina una, de exemplu label_glue_F Într-o manieră similară cu modul în care Julia folosește o marcă de exclamare pentru a semnifica o funcție de mutare în loc; sort! vs sort.
Trecerea funcțiilor este toată furia în programarea funcțională și este modul în care poți face lucruri de genul acesta
sapply(mtcars(, 1:4), mean) # mpg cyl disp hp # 20.09062 6.18750 230.72188 146.68750
Aici trec o listă (primele patru coloane ale mtcars set de date) și a
funcţie (meanpe nume) la sapply ceea ce face în esență un map(l, f)
și produce media fiecăruia dintre aceste coloane, revenind un vector numit al mijloacelor.
Aceasta devine foarte puternică acolo unde este permisă o aplicație parțială, permițând lucruri de genul
ghci> add_5 = (+5) ghci> map (1..10) add_5 (6,7,8,9,10,11,12,13,14,15)
În R, ar trebui să creăm o nouă funcție mai explicit, adică referindu -ne la un arbitrar
add_5 <- (x) x + 5 sapply(1:10, add_5) # (1) 6 7 8 9 10 11 12 13 14 15
Poate că recunoașterea modelului meu a devenit un pic prea suprasolicitată pe ideea că în R „fără paranteze = funcție, nu rezultat; paranteze = rezultat”.
Acest lucru îmi citește ciudat
calc_mean <- function() {
function(x) {
mean(x)
}
}
sapply(mtcars(, 1:4), calc_mean())
Dar este exact același cu exemplul anterior, deoarece calc_mean()
În mod esențial, întoarce a mean funcţie
calc_mean()(1:10) (1) 5.5
Din acest motiv, îmi place ideea de a numi funcția de etichetare, de când am citit acest lucru
p + scale_y_discrete(labels = penguin_label)
ca trecând a funcţie. Paretele se folosesc la locul potrivit – unde a fost numită funcția.
Acum, trebuie să definiți acea variabilă doar pentru a o folosi în scale_y_discrete
Apelul este probabil un pic, așa că da, inlinierea are sens, cu avertismentul care Trebuie să știi că este o funcție.
Nimic din toate acestea nu a fost menit să spună că abordarea {scale} nu este greșită în vreun fel – am vrut doar să abordez propriile mele percepții despre arg = fun() proiecta. Ea face
Ai sens, dar arată diferit. Sunt singur pe asta?
Anunțați -mă despre Mastodon și/sau secțiunea de comentarii de mai jos.
DevTools :: session_info ()
„ `{r sessionInfo, echo = false} devtools :: session_info ()` „
