位元詩人 [Groovy] 程式設計教學:撰寫和使用函式 (Function)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在 Java 中,函式 (function) 一定要包在類別中,所以才會出現靜態函式 (static function) 這種和物件無關但又存在於類別中的函式。

在 Groovy 中,這個限制放寛了,我們可以在 Groovy 中直接撰寫頂層函式,Groovy 會幫我們自動轉為對應的 Java 函式,不需要人為介入。

函式 (Function) 是什麼

函式是一種程序抽象化 (procedure abstraction) 的語法特性,其用途在包住一段程式碼,將其使用有意義的名稱包裝起來,之後我們要調用這段程式碼時,就可以直接透過函式名稱呼叫 (call) 該函式,不用重覆撰寫相同的程式碼。

函式的虛擬碼如下:

returnType fn (param_a, param_b, param_c) {
    /* Implement code here. */
}

由此可知,函式有以下要件:

  • 函式名稱
  • 函式的參數 (parameters):零到多個
  • 函式的回傳值 (returning value):零到一個
  • 函式本體,即實作細節

數學上的函式比較純粹 (pure),都是在進行運算。但電腦程式的函式依其作用分為兩種:

  • 運算 (computing)
  • 狀態 (state) 改變 (註)

第一種函式比較接近數學上的函式,像 java.lang.Math 類別的 sqrt 函式計算某個數字的平方根。對於純運算的函式,只要傳入相同的參數,就會得到相同的結果。

第二種函式則會改變程式的狀態,像是在終端機上印出文字、改變物件的屬性等。對於狀態改變的函式,重覆傳入相同的參數,有可能會得到相異的結果。因為在每輪函式呼叫間,程式的狀態已經改變了。

撰寫函式的原則是同一個函式儘量不要同時具有兩種作用,將進行運算的函式和狀態改變的函式分開來實作。

(註) 在函數式程式設計中,將狀態改變稱為副作用 (side effect)

宣告和使用函式

以下簡例宣告一個函式,並呼叫之:

/* Declare a function. */
void hello ()
{
    println "Hello World"
}

/* Call a function. */
hello()

若不想標註資料型態,可改用以下寫法:

/* Declare a function. */
def hello ()
{
    println "Hello World"
}

/* Call a function. */
hello()

這個函式沒有參數也沒什回傳值,在實務上這類函式用途較少,本例僅用來展示如何建立函式。

帶有參數或回傳值的函式

以下簡例建立一個帶有參數和回傳值的函式:

/* Create a function with parameters
    and a return value. */
int add (int a, int b)
{
    return a + b
}

assert 8 == add(3, 5)

同樣地,若不想標註資料型態,可改用以下寫法:

/* Create a function with parameters
    and a return value. */
def add (a, b)
{
    return a + b
}

assert 8 == add(3, 5)

大部分函式都會有參數,讓我們調整函式的行為。此外,用來進行運算的函式也會有回傳值。

設置函式的預設值 (Default Value)

Groovy 的函式可以加入預設值,如下例:

/* Create a function with a default parameter. */
String greet (name = "World")
{
    "Hello ${name}"
}

/* Call a function with its default argument. */
assert greet() == "Hello World"
/* Call a function with a custom argument. */
assert greet("Michelle") == "Hello Michelle"

預設值會在沒有其他值時自動代入。

遞迴函式 (Recursive Function)

遞迴函式在函式中可呼叫自己,其用意為將問題逐漸縮小。遞迴函式包括兩個要素:

  • 終止條件
  • 遞移步驟

在練習遞迴函式時,通常是沒有掌握好這兩個要素,造成函式無法收斂。

費波那西數是一個常見的遞迴程式範例:

/* Create a recursive function. */
int fib (int n)
{
    if (n <= 1) {
        return n
    }

    fib(n - 1) + fib(n - 2)
}

/* Call a recursive function. */
println fib(10)

要寫好遞迴程式,要有明確的終止條件和遞移步驟。由於遞迴程式在程式設計中時常見且重要的主題,最好還是花一些時間練習。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。