R i programowanie obiektowe

80

Programowanie obiektowe w taki czy inny sposób jest bardzo możliwe w R.Jednak w przeciwieństwie do na przykład Pythona, istnieje wiele sposobów osiągnięcia orientacji obiektowej:

Moje pytanie brzmi:

Jakie główne różnice odróżniają te sposoby programowania obiektowego w R?

Idealnie byłoby, gdyby odpowiedzi tutaj posłużyły jako odniesienie dla programistów R próbujących zdecydować, które metody programowania obiektowego najlepiej odpowiadają ich potrzebom.

W związku z tym proszę o szczegóły, przedstawione w sposób obiektywny, oparty na doświadczeniu, poparty faktami i referencjami. Dodatkowe punkty za wyjaśnienie, w jaki sposób te metody odpowiadają standardowym praktykom OO.

Paul Hiemstra
źródło
1
Informacje o klasach referencyjnych: stackoverflow.com/questions/5137199/ ...
Ari B. Friedman
Dzięki, czy możesz ponownie opublikować link jako odpowiedź? Byłoby miło, gdybyś mógł dołączyć małe podsumowanie tego, czym są klasy referencyjne i dlaczego są lepsze w stosunku do klas S3 / S4.
Paul Hiemstra
Mały ptaszek szepnął mi do ucha, że ​​John Chambers ukaże książkę na ten temat. Ale nie mów nikomu, że to powiedziałem ... ;-)
Dirk Eddelbuettel
1
Czy ten sam mały ptaszek mógłby wkleić poniżej odpowiedź z kilkoma dodatkowymi informacjami o zajęciach Refenence;)
Paul Hiemstra

Odpowiedzi:

34

Klasy S3

  • Nie do końca obiekty, raczej konwencja nazewnictwa
  • Oparty na. składnia: Np. do drukowania, printwywołań print.lm print.anovaitp. A jeśli nie zostanie znaleziona,print.default

Klasy S4

Klasy referencyjne

proto

  • ggplot2 został pierwotnie napisany w proto, ale ostatecznie zostanie przepisany przy użyciu S3.
  • Zgrabna koncepcja (prototypy, nie klasy), ale w praktyce wydaje się skomplikowana
  • Wydaje się, że następna wersja ggplot2 odchodzi od tego
  • Opis koncepcji i realizacji

Klasy R6

  • Przez odniesienie
  • Nie zależy od klas S4
  • Tworzenie klasy R6 jest podobne do klasy referencyjnej, z tą różnicą, że nie ma potrzeby oddzielania pól i metod oraz nie można określić typów pól”.
Ari B. Friedman
źródło
1
Zapraszam do edycji, jeśli masz inne różnice do dodania. Nie będę płakać, jeśli stanie się CW :-)
Ari B. Friedman
3
nie zapomnijlibrary("fortunes"); fortune("strait")
Ben Bolker
1
Dyskusja na temat klas S4 tutaj: stackoverflow.com/questions/3602154/… . Wydaje się, że są one bardziej kłopotliwe niż zapewniają przewagę.
Paul Hiemstra
Co ciekawe, nowe klasy R6 w sposób dorozumiany uznają, że klasami odniesienia były klasy R5, unikając używania tej liczby. Niech spór się zacznie (od nowa).
Ari B. Friedman
1
Nazwa R5 była pierwotnie używana jako żart przez ludzi innych niż twórcy klas referencyjnych. Nazwa R6 jest potwierdzeniem „R5”, ale nie ma na celu sugerować, że nazwa R5 miała jakiekolwiek oficjalne poparcie.
wch
19

Edytuj w dniu 08.03.12: Poniższa odpowiedź odpowiada na część pierwotnie opublikowanego pytania, które zostało usunięte. Skopiowałem to poniżej, aby podać kontekst mojej odpowiedzi:

W jaki sposób różne metody obiektowe są mapowane na bardziej standardowe metody obiektowe używane np. W Javie lub Pythonie?


Mój wkład dotyczy twojego drugiego pytania, w jaki sposób metody OO R są mapowane na bardziej standardowe metody OO. Kiedy myślałem o tym w przeszłości, wracałem raz po raz do dwóch fragmentów, jednego autorstwa Friedricha Leischa, a drugiego Johna Chambersa. Oba wykonują dobrą robotę, wyjaśniając, dlaczego programowanie w stylu OO w R ma inny smak niż w wielu innych językach.

Najpierw Friedrich Leisch z „Tworzenie pakietów języka R: samouczek” ( ostrzeżenie: PDF ):

S występuje rzadko, ponieważ jest zarówno interaktywny, jak i ma system orientacji obiektowej. Projektowanie klas jest oczywiście programowaniem, ale aby uczynić S użytecznym jako interaktywne środowisko analizy danych, ma sens, że jest to język funkcjonalny. W „prawdziwych” językach programowania obiektowego (OOP), takich jak C ++ lub Java, definicje klas i metod są ze sobą ściśle powiązane, metody są częścią klas (a zatem obiektów). Chcemy przyrostowych i interaktywnych dodatków, takich jak metody zdefiniowane przez użytkownika dla wstępnie zdefiniowanych klas. Te dodatki można wprowadzić w dowolnym momencie, nawet w locie, w wierszu polecenia, gdy analizujemy zestaw danych. S stara się znaleźć kompromis między orientacją na obiekt a interaktywnym użyciem i chociaż kompromisy nigdy nie są optymalne w odniesieniu do wszystkich celów, do których dąży, w praktyce często działają zaskakująco dobrze.

Drugi fragment pochodzi ze znakomitej książki Johna Chambersa „Oprogramowanie do analizy danych” . ( Link do cytowanego fragmentu ):

Model programowania OOP różni się od języka S we wszystkich punktach oprócz pierwszego, mimo że S i niektóre inne języki funkcjonalne obsługują klasy i metody. Definicje metod w systemie OOP są lokalne dla klasy; nie ma wymogu, aby ta sama nazwa metody oznaczała to samo dla niepowiązanej klasy. W przeciwieństwie do tego definicje metod w R nie znajdują się w definicji klasy; koncepcyjnie są one związane z funkcją ogólną. Definicje klas są wprowadzane przy określaniu wyboru metody, bezpośrednio lub poprzez dziedziczenie. Programiści przyzwyczajeni do modelu OOP są czasami sfrustrowani lub zdezorientowani, że ich programowanie nie przenosi się bezpośrednio do R, ale nie może. Funkcjonalne użycie metod jest bardziej skomplikowane, ale także bardziej dostosowane do posiadania znaczących funkcji i nie można go zredukować do wersji OOP.

Josh O'Brien
źródło
14

S3 i S4 wydają się być oficjalnymi (tj. Wbudowanymi) podejściami do programowania obiektowego. Zacząłem używać kombinacji S3 z funkcjami osadzonymi w funkcji / metodzie konstruktora. Moim celem było uzyskanie składni typu object $ method (), tak aby mieć półprywatne pola. Mówię półprywatne, ponieważ nie da się ich tak naprawdę ukryć (o ile wiem). Oto prosty przykład, który tak naprawdę nic nie robi:

#' Constructor
EmailClass <- function(name, email) {
    nc = list(
        name = name,
        email = email,
        get = function(x) nc[[x]],
        set = function(x, value) nc[[x]] <<- value,
        props = list(),
        history = list(),
        getHistory = function() return(nc$history),
        getNumMessagesSent = function() return(length(nc$history))
    )
    #Add a few more methods
    nc$sendMail = function(to) {
        cat(paste("Sending mail to", to, 'from', nc$email))
        h <- nc$history
        h[[(length(h)+1)]] <- list(to=to, timestamp=Sys.time())
        assign('history', h, envir=nc)
    }
    nc$addProp = function(name, value) {
        p <- nc$props
        p[[name]] <- value
        assign('props', p, envir=nc)
    }
    nc <- list2env(nc)
    class(nc) <- "EmailClass"
    return(nc)
}

#' Define S3 generic method for the print function.
print.EmailClass <- function(x) {
    if(class(x) != "EmailClass") stop();
    cat(paste(x$get("name"), "'s email address is ", x$get("email"), sep=''))
}

I trochę kodu testowego:

    test <- EmailClass(name="Jason", "[email protected]")
    test$addProp('hello', 'world')
    test$props
    test
    class(test)
    str(test)
    test$get("name")
    test$get("email")
    test$set("name", "Heather")
    test$get("name")
    test
    test$sendMail("[email protected]")
    test$getHistory()
    test$sendMail("[email protected]")
    test$getNumMessagesSent()

    test2 <- EmailClass("Nobody", "[email protected]")
    test2
    test2$props
    test2$getHistory()
    test2$sendMail('[email protected]')

Oto link do wpisu na blogu, który napisałem o tym podejściu: http://bryer.org/2012/object-oriented-programming-in-r Z zadowoleniem przyjmuję komentarze, krytykę i sugestie dotyczące tego podejścia, ponieważ nie jestem przekonany siebie, jeśli to najlepsze podejście. Jednak problem, który próbowałem rozwiązać, działał świetnie. W szczególności w przypadku pakietu makeR ( http://jbryer.github.com/makeR ) nie chciałem, aby użytkownicy bezpośrednio zmieniali pola danych, ponieważ potrzebowałem upewnić się, że plik XML reprezentujący stan mojego obiektu pozostanie zsynchronizowany. Działało to doskonale, o ile użytkownicy przestrzegali zasad, które zarysowałem w dokumentacji.

jbryer
źródło
10
W pewnym sensie ponownie wymyślasz klasy referencyjne „ręcznie” za pomocą powyższego kodu ... To po prostu czyni sprawę bardziej kruchą.
Simon Urbanek
Dzięki Simon. Nie wiedziałem o ReferenceClasses, dopóki tego nie opublikowałem.
jbryer