解析哈囉世界
在約維安計畫:R 語言起步走我們透過 print("Hello, World!") 來
確認 R 語言的開發環境及執行環境是否已經安裝妥當,在這一段哈囉世界的程式碼中,其實就能體會我們引用 Greg Martin 對於 R 語言核心精神的詮釋:對資料結構應用函數。
The way R works is pretty straightforward, you apply functions to objects.
print
是函數的名稱,print()
則表示要應用該函數的功能,而 “Hello, World!“
則是一個被稱呼為文字(Character)的向量資料結構;換言之,哈囉世界可以表達為:對 “Hello, World!“
這個文字向量應用了 print()
函數。
print("Hello, World!")
什麼是函數
函數是構成所有程式語言的基本要素,一個函數是一段「被命名」的程式碼,這段程式碼可以用於執行某個特定任務,可能是數值的運算或者文字的處理,當使用者想要應用(使用、呼叫)某一個函數之前,必須先確定在欲產生作用的範疇中該函數已經被定義或者被載入。簡而言之,在哈囉世界的程式碼中,之所以能夠對字串 “Hello, World!“
應用 print()
函數,是因為 print()
函數屬於 R 語言的內建函數,在 R 語言啟動的當下就會被載入供我們使用。
函數的來源
R 語言使用者除了能夠應用內建函數以外,還可以從其他兩個管道取得所需要的函數:
套件(Packages)。
自行定義。
自行定義函數需要考慮五個組成要件:
函數名稱。
輸入。
參數。
函數主體。
輸出。
看似抽象,不過想想平日購買珍珠鮮奶茶的流程,就像是函數的運作一般:
函數名稱:購買珍珠鮮奶茶。
輸入:中杯 35 元;大杯 50 元。
參數:甜度(無糖、微糖、半糖、少糖、全糖)與冰塊(去冰、少冰、全冰)。
函數主體:點餐、貼標籤、加珍珠、加冰塊、倒茶、加鮮奶一直到封口。
輸出:珍珠鮮奶茶。
function_name <- function(INPUTS, PARAMETERS){
# body of the function
# ...
# ...sequence of statements
return(OUTPUTS)
}
首先要給函數取個名字(function_name
),利用保留字 function
告訴 R 語言這是一個函數,接著在小括號中放入想好的輸入(INPUTS
)與參數(PARAMETERS
),然後在大括號內縮排撰寫我們主要的程式(body of the function),最後是將輸出(OUTPUTS)放在 return()
裡頭。#
是用來標記註解的符號,有 #
標記的該行敘述將不會被 R 語言執行,這讓寫程式的人能夠用口語來描述這段程式的目的是什麼,如果將哈囉世界改寫為函數形式,每次只需要輸入函數名稱並加上小括號 hello_world()
,就可以獲得 “Hello, World!“
這個文字向量為輸出。
hello_world <- function(){
return("Hello, World!")
}
print(hello_world())
輸入與參數的差別
function_name <- function(INPUTS, PARAMETERS){
# body of the function
# ...
# ...sequence of statements
return(OUTPUTS)
}
在 R 語言中輸入會設計為「無」或者「必要」,參數則會給予預設值,例如 Sys.Date()
函數或 getwd()
都是無需輸入的函數,函數名稱後的小括號留空白。
print(Sys.Date())
print(getwd())
例如
sort()
函數具有 decreasing
參數,預設值給定為 FALSE
。所以在沒有傳入的狀況下,其實就是採用 decreasing = FALSE
的參數設定,假使不想採用預設值,就能指定參數 decreasing = TRUE
。
primes <- c(11, 5, 7, 2, 3)
print(sort(primes))
print(sort(primes, decreasing = TRUE))
更動結果的機制
使用「函數」可以讓應用的對象物件造成更動,更動方式是以「回傳值」型態輸出更動後的結果,延續前述 sort()
函數的例子,sort(
primes)
是以回傳值輸出排序後的向量,如果沒有將回傳值更新原本命名的向量,排序的更動並不會被保留。
primes <- c(11, 5, 7, 2, 3)
print(sort(primes))
sort(primes) # Apply sort function to primes
print(primes) # primes is not sorted
primes <- sort(primes) # Update primes with function output
print(primes) # primes is sorted
作用域
在約維安計畫:R 語言的流程控制我們提到了程式區塊(Code blocks)的觀念,不論是條件判斷或者迴圈,都仰賴一對大括號(A curly-brace pair)建立出程式區塊,並以此作為敘述的附屬。在自行定義函數時也同樣以程式區塊建構出函數主體,不過更特別的是,附屬於 function
保留字的程式區塊具有一個稱為作用域(或稱範疇 Scope)的觀念,在附屬於函數下的程式區塊被稱為 Local scope,函數以外的部分則稱為 Global scope。在 Local scope 中我們可以運用 INPUTS、PARAMETERS 來進行運算並宣告新的物件,但是這都侷限於 Local scope 之中,一但在 Global scope 意圖運用前述的這些物件,都會遭遇到錯誤,意即在 Global scope 中這些物件「沒有作用」,這也是作用域一詞的由來。例如在 Global scope 中試圖印出任何在 Local scope 中可以運用的物件都會遭遇錯誤。
is_factor <- function(a, b){
modulo <- a %% b
out <- modulo == 0
return(out)
}
print(is_factor(4, 2))
print(a)
## Error in print(a) : object 'a' not found
print(b)
## Error in print(b) : object 'b' not found
print(modulo)
## Error in print(modulo) : object 'modulo' not found
print(out)
## Error in print(out) : object 'out' not found
return() 的作用
初次學習自行定義函數多半都不甚暸解 return()
作用,原因是先前太過頻繁地使用 print()
函數將文字處理、數值計算或者資料結構操作的結果顯示出來,導致在自行定義函數時不習慣去使用 return()
。事實上,return()
具有兩個作用,第一個跟字面上意義相同的將函數結果「輸出」,假如在自行定義函數時只有將運算結果用 print()
函數顯示出來,該函數就不具備能將結果儲存在物件中的能力,變成只能夠在使用時顯示結果,卻不能將結果儲存起來。第一個特性在 R 語言中可能比較難感受到,原因是 R 語言為了幫助使用者,會預設將函數主體區塊中的最後一行物件輸出,例如下列自行定義函數並沒有指定將判斷因數的結果輸出,依然能得到預期結果
。
is_factor <- function(a, b){
modulo <- a %% b
out <- modulo == 0
}
function_output <- is_factor(4, 2)
print(function_output)
第二個是終止程式區塊的執行,在函數程式區塊中寫在 return()
之後的所有內容在使用函數的時候都不會有任何作用。
is_factor <- function(a, b){
modulo <- a %% b
return(modulo)
out <- modulo == 0 # will not be returned
print(out) # # will not be printed
}
function_output <- is_factor(4, 2)
print(function_output)
輸入與輸出的對應關係
自行定義函數的重點在於規劃輸入與輸出的對應關係,如同在數學課中學過的「函數映射關係」,只要釐清了自行定義函數針對輸入與輸出的設計機制,剩下的任務就僅是將程式區塊的函數主體完成而已。
從輸入開始,函數可以接受沒有輸入與參數的設計,在小括號中留空即可。
hello_world <- function(){
return("Hello, World!")
}
print(hello_world())
函數當然可以接受有輸入與參數的設計,好的函數設計會在參數給予合適的預設值。
hello_someone <- function(someone = "World"){
out <- sprintf("Hello, %s!", someone)
return(out)
}
print(hello_someone())
print(hello_someone(someone="Function"))
設計接受多個輸入值的函數時,因為 R 語言的向量、元素運算(Element-wise)特性,直接使用合適的資料結構容納多個輸入資料即可。
square_multiple_integers <- function(x){
return(x**2)
}
print(square_multiple_integers(c(2, 3)))
print(square_multiple_integers(c(2, 3, 5)))
多個輸出的處理同樣是採用適當的資料結構儲存,像是用 list
儲存兩個向量的相加與相減。
add_subtract_vectors <- function(a, b){
out <- list(
"add" = a + b,
"subtract" = a - b
)
return(out)
}
function_output_as_list = add_subtract_vectors(3, 4)
print(function_output_as_list)
在認識了函數的各種眉眉角角之後,第三十週約維安計畫:R 語言自行定義函數來到尾聲,希望您也和我一樣期待下一篇文章。
對於這篇文章有什麼想法呢?喜歡😻、留言🙋♂️或者分享🙌