深入R語言Object-Oriented Programming(一):S3, S4, R6, Reference Class

對於想要擁有自己開發R package能力或是參與熱門套件的開發,深入了解R語言Object-Oriented Programming(OOP)的機制非常重要,另外,對於別人寫的package在使用邏輯上會理解更快,也能逆向去看別人如何開發的,尤其是Bioconductor裡面針對生物資訊處理,常常會要有特殊的資料結構,因此會有自己定義的class和object,以及相關的methods,會讓人一開始使用上“搞不清楚作者在想什麼”,但知道這些都是依循R內OOP規則的,就能拆開來去看其怎麼定義的,更深入理解一個別人的package。所以懂R中OOP規則,有兩大幫助:


1. 提升自己開發package的能力。
2. 提升理解別人package的能力。

R語言本身就是“實用性導向”,且快速在演進中,不斷加入更多“電腦科學”的觀念,為了滿足更複雜的計算需求,但相對於資料分析時,我們會比較偏向使用functional programming的方式來思考,在進一步當我們想要打造一些工具幫助別人分析時,使用Object-Oriented Programming就是一個不錯的方式。

在R發展的歷程中,其能使用的範圍越來越廣,工具越來越多,代碼維護上很難以functional programming的方式來做,而面向對象編程則可以解決這類問題,因此衍生出許多OOP系統,像是OOP, R.oo, mutatr, R5, proto等等都是被提出來的面向對象編程系統(值得注意的是目前ggplot2在底層上使用了由proto為基底的ggproto,所以想要自己設計geom/stat的話就必須理解一下他的結構)

目前R中最熱門的OOP系統主要有四種:S3,S4,R6,Reference Class

大致來說S3,S4都是基於的泛型函數(generic function),而R6, Reference Class則是完全面向對象,R6則在建立物件時稍微寬鬆

S3:最基本的系統,幾乎是base R操作中都會用到的概念

使用generic-function Object Oriented風格,並沒有很正式的方式來定義class,也就是說必須從新定義其對於不同class的反應

#S3系統中定義class的方式非常的隨意(arbitrarily),可以直接指定某個物件的class或是用structure()來定義
DNA_sequence <-- structure("ATCG", class = "DNA")
class(DNA_sequence) <-  "DNA"
#當然也可以使用稍微嚴謹的方式,用constructur function
DNA_sequence_constructor <- function(sequence){
 structure(list(sequence = sequence), class = "DNA")
}
#在來於S3中generic methods系統如下操作
count_basepair <- function(x) UseMethod("count_basepair")
count_basepair.dna <- function(x){
   return(nchar(x$sequence))
}
count_basepair(DNA_sequence)
[1] 4

S4:對於理解bioconductor中的很多資料形式會很重要,因為他們主要使用S4來搭建

跟S3風格相似,其有對於class的正式定義,且帶入inheritance和method、generic的概念,另外其具有multiple dispatch,代表generic function可以使用各種class的參數。

#使用S4系統時,用setClass來定義一個class
setClass("DNA",
    slots = list(sequence="character",
                 species="character")
)
#創建屬於某個class的object時,使用new
sample_dna <- new("DNA",
                   sequence="AATCCCGCG",
                   species="homo sapiens")

#S4使用@來subset一個object中的slots
sample_dna@sequence

#使用setGeneric來創建generic function
setGeneric("count_sequence_basepair", function(x){standardGeneric("count_sequence_basepair")})

#創建後,用setMethod來define這function實際的功能
setMethod("count_sequence_basepair",
c(x = "dna"),
function(x){return(nchar(x@sequence)
})

Reference Class:比較新的語法,借鑑了如java,c#等程式的特性

採用message-passing Object-oriented,在區別class、method、function間的關係更嚴謹,而RC object本身是mutable的,且是modified in place。

#Reference Class在定義class時後使用setRefClass(),此時也會定義了這個class所具有的method
DNA <- setRefClass("dna",
                   fields = list(sequence="character",
                                 species="character")
                    methods=list(
                     count_sequence_basepair = function(){
                        return(nchar(sequence))
                     )
)
#創建object的方式
my_sample <- DNA$new(sequence="ATATACGCG", species="homo sapiens")

#使用method的方式
my_sample$count_sequence_basepair()
[1] 9
&lt;span style="color: #000000;"&gt;

觀察不同R裡面OOP系統蠻有趣的,也會發現整個語言的變化,在Reference Class中,可以看到很嚴謹的定義,其實Reference Class蠻強大的,且帶入很多新的觀念,值得再深入研究。

這部分更深入的理解可以看John M. Chambers在2016年新書:Extending R,裡面談了很多這位R core team對於整個R架構的看法,書中的內容可以分成三部分,第一部份講述R的變化,第二部分談R的programming、package、function觀念,第三部分則談論R的Object-Oriented Programming,他把S3,S4歸類為Functional Object-Oriented Programming,而Reference Class這比較新的系統叫做Encapsulated Object-Oriented Programming。最後書裡面提到關於Interface的觀念,主要便是奠基在Reference Class的架構上,能跟其他語言的對接,尤其是Python和Julia。

screenshot.png

除了這本Extending R外, Advance R, Hadley Wickham 中很詳細地談論了OOP system

這邊則有比較零星的討論或是簡報可以看

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s