Generatoare de funcții și aplicare parțială în r

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

Î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”. 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 ()` „

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.