對於想要擁有自己開發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 <span style="color: #000000;">
觀察不同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。
除了這本Extending R外, Advance R, Hadley Wickham 中很詳細地談論了OOP system
這邊則有比較零星的討論或是簡報可以看
- John’s presentation in November 2010 at Stanford
- Martin Morgan in November 2010 from the BioConductor Europe meetings
- Romain and myself mention it in the Google Tech Talk on Rcpp
-
R:Introduction to Reference Classes, Aleix Ruiz de Villa
-
A short introduction to S4, Christophe Genolini
- A practical tutorial on S4 programming, Laurent Gatto
- stackover 問題串