位元詩人 [Nim] 程式設計教學:高階函式 (Higher Order Function)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

高階函式 (higher-order function) 是指用函式為參數或回傳值的函式,像前面的閉包就是一種高階函式。在本文中,我們介紹一些常見的高階函式的模式。我們不依賴現有的套件,而會重新實作,讓各位讀者參考。

all

all 接受一個序列和一個程序,當序列中所有元素皆符合該程序的條件時,回傳 true,反之,則回傳 false。範例如下:

proc all[T](arr: seq[T], f: proc (n: T): bool): bool =
    for e in arr:
        if not f(e):
            return false

    true

let a1 = @[1, 2, 3, 4, 5, 6]
assert(a1.all(proc (n: int): bool = n > 0) == true)

let a2 = @[-2, -1, 0, 1, 2, 3]
assert(a2.all(proc (n: int): bool = n > 0) == false)

any

any 接受一個序列和一個程序,當序列中其中一個元素符合該程序的條件時,回傳 true,反之,則回傳 false。範例如下:

proc any[T](arr: seq[T], f: proc (n: T): bool): bool =
    for e in arr:
        if f(e):
            return true

    false

let a = @[-2, -1, 0, 1, 2, 3]
assert(a.any(proc (n: int): bool = n > 0) == true)

let b = @[1, 3, 5, 7, 9]
assert(b.any(proc (n: int): bool = n mod 2 == 0) == false)

filter

any 接受一個序列和一個程序,從原序列中過濾條件,將符合該程序的元素回傳至新序列。範例如下:

proc filter[T](arr: seq[T], f: proc (n: T): bool): seq[T] =
    result = @[]

    for e in arr:
        if f(e):
            result.add(e)

let arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let even = arr.filter(proc (n: int): bool = n mod 2 == 0)
assert(even == @[2, 4, 6, 8, 10])

map

map 接受一個序列和一個程序,根據該程序將元素轉換後回傳至新序列。範例如下:

proc map[T](arr: seq[T], f: proc (n: T): T): seq[T] =
    result = @[]

    for e in arr:
        result.add(f(e))

let arr = @[1, 2, 3, 4, 5]
let sqr = arr.map(proc (n: int): int = n * n)
assert(sqr == @[1, 4, 9, 16, 25])

reduce

reduce 接受一個序列和一個程序,依照該程序將序列縮減為單一值。範例如下:

proc reduce[T](arr: seq[T], f: proc (a: T, b: T): T): T =
    assert(arr.len > 0)

    if arr.len == 1:
        return arr[0]

    result = arr[0]

    for i in countup(1, arr.len - 1):
        result = f(result, arr[i])

let arr = @[1, 2, 3, 4, 5]
let sum = arr.reduce(proc (a: int, b: int): int = a + b)
assert(sum == 15)

zip

zip 接收兩個等長的序列,將兩序列中同位置的元素合成一個元組,回傳一個以元組為元素的序列。範例如下:

proc zip[T, S](a: seq[T], b: seq[S]): seq[tuple[first: T, second: S]] =
    assert(a.len == b.len)

    result = @[]

    for i in countup(0, a.len - 1):
        result.add((a[i], b[i]))

let a = @[1, 2, 3]
let b = @["a", "b", "c"]

let zipped = zip(a, b)
assert(zipped == @[(first: 1, second: "a"), (first: 2, second: "b"), (first: 3, second: "c")])

partition

partition 接收一個序列和一個程序,根據該程序將原序列拆開,回傳兩個新序列。範例如下:

註:兩序列長度可能不相等。

proc partition[T](arr: seq[T], f: proc(n: T): bool): tuple[matched: seq[T], unmatched: seq[T]] =
    result = (matched: @[], unmatched: @[])

    for e in arr:
        if f(e):
            result.matched.add(e)
        else:
            result.unmatched.add(e)

let arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let (evens, odds) = arr.partition(proc (n: int): bool = n mod 2 == 0)
assert(evens == @[2, 4, 6, 8, 10])
assert(odds == @[1, 3, 5, 7, 9])

組合數個高階函式

高階函式間可相互組合,達到加乘的效果,範例如下:

# Declare filter as above.

# Declare map as above.

# Declare reduce as above.

let arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let o = arr
    .filter(proc (n: int): bool = n mod 2 != 0)
    .map(proc (n: int): int = n * n)
    .reduce(proc (a: int, b: int): int = a + b)

assert(o == 1 * 1 + 3 * 3 + 5 * 5 + 7 * 7 + 9 * 9)
關於作者

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

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