由於 Nim 既不支援多重繼承 (multiple inheritance) 也不支援介面 (interface),Nim 對於多型的支援相對薄弱。不支援多型的話,很多設計模式 (design patterns) 會難以實作,希望 Nim 以後可以改善這方面的議題。
Duck Type
Duck Type 是指專注在物件的外在行為上,不用過度地檢查物件實際的型別;動態型別語言,像是 Python 或 Ruby,可自動實現這個想法,靜態型別語言則要透過一些額外的手法。現階段,可以使用繼承來達成子類型 (subtyping) 的效果,如下例:
type
Animal* = ref object of RootObj
method speak*(a: Animal) {.base.} =
quit "Unimplemented"
type
Duck* = ref object of Animal
method speak*(d: Duck) =
echo "Pack pack"
proc newDuck*(): Duck =
new(result)
type
Dog* = ref object of Animal
method speak*(d: Dog) =
echo "Wow wow"
proc newDog*(): Dog =
new(result)
type
Tiger* = ref object of Animal
method speak*(t: Tiger) =
echo "Halum halum"
proc newTiger*(): Tiger =
new(result)
when isMainModule:
let animals: seq[Animal] = @[newDuck(), newDog(), newTiger()]
for a in animals:
a.speak
表面上看起來,似乎可以達到 duck typing 的效果;但是,隨著物件變多,單一繼承往往就會不太夠用,而 Nim 目前沒有官方的介面的方案,這是目前 Nim 較為不足的地方。
函式重載
使用 method
即可達成函式重載 (funciton overloading) 或方法重載 (method overloading) 的效果,見上例。
用 tuple 模擬介面
單一繼承的程式語言,大部分都會用介面、mixin、trait 等替代機制補足需要多重繼承的需求,但 Nim 到目前為止缺乏這一塊,有一個非正式的方法是透過帶有方法宣告的 tuple 來模擬介面,官方手冊沒有記錄這一點,而出現在某篇國外的部落格文章。我們這裡將先前的例子改寫,加入介面的支援:
import random
# Some interface in Nim
type
IEmployee = tuple[
salary: proc (): float
]
type
Employee* = ref object
s: float
proc salary*(e: Employee): float =
e.s
proc `salary=`*(e: Employee, salary: float) =
assert(salary >= 0.0)
e.s = salary
proc newEmployee*(salary: float): Employee =
new(result)
result.s = salary
# Type conversion, from Employee to IEmployee.
proc toEmployee*(e: Employee): IEmployee =
return (
salary: proc (): float =
e.salary
)
type
Programmer* = ref object
plns: seq[string]
pes: seq[string]
pee: Employee
proc salary*(p: Programmer): float =
p.pee.salary
proc `salary=`*(p: Programmer, salary: float) =
assert(salary >= 0.0)
p.pee.salary = salary
proc langs*(p: Programmer): seq[string] =
p.plns
proc `langs=`*(p: Programmer, langs: seq[string]) =
p.plns = langs
proc editors*(p: Programmer): seq[string] =
p.pes
proc `editors=`*(p: Programmer, editors: seq[string]) =
p.pes = editors
proc solve*(p: Programmer, problem: string) =
randomize()
let ln = p.langs[random(p.langs.low..p.langs.len)]
let e = p.editors[random(p.editors.low..p.editors.len)]
echo "The programmer solved " & problem & " in " & ln & " with " & e
proc newProgrammer*(langs: seq[string], editors: seq[string], salary: float): Programmer =
new(result)
result.plns = langs
result.pes = editors
result.pee = newEmployee(salary = salary)
# Type conversion, from Programmer to IEmployee.
proc toEmployee*(p: Programmer): IEmployee =
return (
salary: proc (): float =
p.salary
)
# Main program.
when isMainModule:
let ee = newEmployee(
salary = 500)
let pr = newProgrammer(
langs = @["Go", "Rust", "D", "Nim"],
editors = @["Atom", "Sublime Text", "Visual Studio Code"],
salary = 100)
let es = @[ee.toEmployee, pr.toEmployee]
for e in es:
echo e.salary()
從這裡可以看出來,目前在 Nim 語言中,要自己手動轉換成介面的型別;由於 Nim 的語法還沒到 1.0 版,日後有可能變動。
運算子重載
運算子重載 (operator overloading) 算是一種使用者自訂的語法糖,讓自訂類別看起來像內建類別,在一些情境中相當實用,像是實作新的數字型別等。Nim 對運算子重載支援良好,其實,Nim 的內建運算子也是用程序來實作,運算子重載則是由使用者實作某個運算子。以下範例實作數學的向量 (vector) 類別,在裡面重載了三個運算子,分別是陣列 getter、陣列 setter、加法運算子:
type
Vector = ref object
arr: seq[float]
proc len*(v: Vector): int =
v.arr.len
proc `[]`*(v: Vector, i: int): float =
v.arr[i]
proc `[]=`*(v: Vector, i: int, e: float) =
v.arr[i] = e
proc newVector*(args: varargs[float]): Vector =
new(result)
result.arr = @[]
for e in args:
result.arr.add(e)
proc withSize*(s: int): Vector =
new(result)
result.arr = @[]
for i in countup(1, s):
result.arr.add(0.0)
proc fromArray*(arr: openArray[float]): Vector =
new(result)
result.arr = @[]
for e in arr:
result.arr.add(e)
proc `+`*(a: Vector, b: Vector): Vector =
assert(a.len == b.len)
result = withSize(a.len)
for i in countup(0, a.len - 1):
result[i] = a[i] + b[i]
when isMainModule:
let v1 = newVector(1.0, 2.0, 3.0)
let v2 = fromArray(@[2.0, 3.0, 4.0])
let v = v1 + v2
assert(v[0] == 3.0)
assert(v[1] == 5.0)
assert(v[2] == 7.0)
在最下方的主程式可看出,透過運算子重載,使得語法看起來更自然。運算子重載比較不是必備的特性,而算是語法上加分項目,許多語言支援運算子重載,但仍然有些現代語言不支援,像是 Java 或 Go 等。
泛型
泛型 (generics) 是一種重覆使用演算法的語法機制,可以將同一套程式碼套用在不同型別上,很常用於實作資料結構 (data structures) 或容器 (collections) 中;Nim 對泛型支援良好,且易於使用。以下範例將向量 (vector) 重新以泛型改寫:
type
Vector[T] = ref object
arr: seq[T]
proc len*[T](v: Vector[T]): int =
v.arr.len
proc `[]`*[T](v: Vector[T], i: int): T =
v.arr[i]
proc `[]=`*[T](v: Vector[T], i: int, e: T) =
v.arr[i] = e
proc newVector*[T](args: varargs[T]): Vector[T] =
new(result)
result.arr = @[]
for e in args:
result.arr.add(e)
proc withSize*[T](s: int): Vector[T] =
new(result)
result.arr = @[]
for i in countup(1, s):
result.arr.add(T(0))
proc fromArray*[T](arr: openArray[T]): Vector[T] =
new(result)
result.arr = @[]
for e in arr:
result.arr.add(e)
proc `+`*[T](a: Vector[T], b: Vector[T]): Vector[T] =
assert(a.len == b.len)
result = withSize[T](a.len)
for i in countup(0, a.len - 1):
result[i] = a[i] + b[i]
when isMainModule:
let v1 = newVector[float](1.0, 2.0, 3.0)
let v2 = fromArray[float](@[2.0, 3.0, 4.0])
let v = v1 + v2
assert(v[0] == 3.0)
assert(v[1] == 5.0)
assert(v[2] == 7.0)
由於泛型相當實用,很多現代語言都加入此機制;像是 C# 和 Java 原先不支援泛型,但在後續版本中加入這項特性。