Go Interface & Polymorphism

Gary Liao
14 min readMay 29, 2023

官方文件

Interfaces

Interface 本身是一種 type。

Interface 定義了一個type 集合。

An interface type defines a type set.

宣告為 interface 類型的變數,其可以儲存的值之類型,屬於該interface所定義的 type 集合。

A variable of interface type can store a value of any type that is in the type set of the interface.

這些類型被稱為實作了這個interface。

Such a type is said to implement the interface.

未初始化的 interface 變數其值為 nil。

The value of an uninitialized variable of interface type is nil.

Interface 是由元素清單組成。元素可能是 method 、type 或 type union。

An interface type is specified by a list of interface elements. An interface element is either a method or a type element, where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.

Basic interfaces

Interface 的基本形式是由不特定多數的 methods 所組成 (有可能是0個)。Interface 所定義的type 集合,都是實作了這個 interface 內所有的methods。只由 methods 所組成的 interface,稱為 basic interface。

不特定多數的 type 可以實作同一個 interface,只要他們有做到 interface 所要求的 methods 即可,也不管他們有沒有 interface 所定義之外的 methods。

一個 type 可以同時實作多個 interfaces,例如所有 type 都實作了 empty interface interface {} ,為了方便,any是 empty interface 的別名。

只有 basic interface 可以用於宣告變數或值。

Interfaces that are not basic may only be used as type constraints, or as elements of other interfaces used as constraints. They cannot be the types of values or variables, or components of other, non-interface types.

Embedded interfaces

Interface 的元素中有別的 interface,就是 embedded interface,其定義之 type 集合必須實作所有巢狀結構中的 interfaces。 一旦 interface 底下有非 basic interface,則其自身亦不是 basic interface。

General interfaces

廣義而言,任何 type 都可以組成interface,但只有 basic interface 可以用於宣告變數或值,非 basic interface 只能用於 type constraints

type xxx { int }
// [T xxx] 是這個function 的 type parameters
// xxx 用在這裡就是 type constraints
func Swap[T xxx](a, b *T) {
*a, *b = *b, *a
}
  • Empty interface 所定義的 type 集合,是所有非 interface 的 types。
  • 有特定 method 組成之 interface 所定義的 type 集合,都包含該method。
  • interface 與其底下之 interface 的 type 集合的交集,才是該 interface 所定義的 type 集合。
  • interface 的組成若只有一個非 interface 的 type ,那麼其 type 集合就只有那個 type。
// An interface representing only the type int.
interface { int }
  • interface 的組成若為 ~T 的形式,則其type 集合之底層 type必須為T,且T 不得為 interface,亦不得為泛型 (generic type)。
// An interface representing all types with underlying type int.
interface { ~int }
  • 不特定多數個 T 與 ~T 亦可以 “|” 符號組成聯集,且聯集之各元素之間之交集必須為空集合。
type Float interface {
float32 | float64

}
Type f interface{
float32 | Float // illigal, Float interface contains float32
}

例如這個非 basic interface 定義了底層必須為 int 且必須有String method:

// An interface representing all types with underlying type int that implement the String method.
interface {
~int
String() string
}

例如這個 interface 定義了空集合,因為不會有任何type 同時滿足 int 與 string:

// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
int
string
}

這個 非 basic interface 定義了底層只要是 float32 或 float64 都可以:

type Float interface {
~float32 | ~float64
}

Go 是 Duck Typing 嗎?

Go 是 structural typing,在 compile 階段檢查;duck typing 是在runtime 檢查。

範例

只要能 Suck 的都是 Baby :

package main

import (
"fmt"
)

type Baby interface {
Suck() string
}

type HumanBaby struct {
Name string
hungry bool
}

func (b *HumanBaby) Suck() string {
b.hungry = false
return "bo~bo~bo~"
}

func CallForMom(b Baby) string {
b.Suck()
return "Mom is feeding ... "
}

func main() {
Ted := HumanBaby{Name: "Ted", hungry: true}
fmt.Printf("%+v \n", Ted) // {Name:Ted hungry:true}
fmt.Println(CallForMom(&Ted)) //Mom is feeding ...
fmt.Printf("%+v \n", Ted) // {Name:Ted hungry:false}
}

Empty interface 的範例:

package main

import (
"fmt"
)

func main() {
// 1: int
// '2': int32
// "3": string
// true: bool
// 4.0: float64
ary := []any{1, '2', "3", true, 4.0} // any : interface{}
fmt.Println(ary) // [1 50 3 true 4]
fmt.Println(SprintAny(ary)) // [1 2 3 true �]
}

func SprintAny(ary []any) string {
str := "["
for k, v := range ary {
if k > 0 {
str += " "
}

switch v.(type) {
case int:
str += fmt.Sprintf("%d", v)
case int32:
str += string(v.(int32))
case bool:
str += fmt.Sprintf("%v", v)
case string:
str += v.(string)
default:
str += "\ufffd"
}
fmt.Printf("%T\n", v)
}
str += "]"
return str
}

多型 polymorphism

多型 (polymorphism) 的定義 (from wiki) :

In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types[1] or the use of a single symbol to represent multiple different types.[2]

本質

從本質上來說,多型就是為多種 types 提供單一的介面 (interface) 或符號 (symbol)。

目的

  • Code Reusability: 同一段程式碼可以應用於不同 types。
  • Abstraction: 隱藏底層各自實作,表層統一介面,使代碼易於理解維護。
  • Flexibility: 讓程式可以在 runtime 抽換不同物件。
  • Decoupling: 降低物件之間的相依性。

形式

不同語言用不同的形式達成這個概念 :

  • Ad hoc polymorphism: 針對不同 types 實作一對一的代碼,但表面上是相同名稱的 functions 或 operators。實踐方式如 function overloading 與operator overloading。
  • Parametric polymorphism: 同一套代碼可以對不同 types 運行,許多物件導向語言使用泛型 (generics) 來實踐之,來做到 type checking,如 C++、C#、Java。
  • Subtyping: 透過限制底層 types 的範圍來確保執行的正確性。

還有許多其他形式,但就不一一列了。

範例

Ad hoc polymorphism:

Go 透過 interface 來限制 types 必須實作特定 methods,而不同 types 的同名method 擁有各自不同的實作。

例如下例,必須要擁有 Describe method 才符合 Describer 這個 interface,才能被傳遞到 describe 這個 function 執行;而 數值 與 字串,由於底層的資料型態不同,必須得各自實作不同的 Describe method。

package main

import "fmt"

// Describer interface
type Describer interface {
Describe() string
}

// IntData struct
type IntData struct {
Value int
}

// Describe method for IntData
func (d IntData) Describe() string {
return fmt.Sprintf("Integer: %d", d.Value)
}

// StringData struct
type StringData struct {
Value string
}

// Describe method for StringData
func (d StringData) Describe() string {
return fmt.Sprintf("String: %s", d.Value)
}

// describe function that takes a Describer and calls its Describe method
func describe(d Describer) {
fmt.Println(d.Describe())
}

func main() {
i := IntData{Value: 5}
s := StringData{Value: "Hello"}

describe(i) // Outputs: Integer: 5
describe(s) // Outputs: String: Hello
}

Parametric polymorphism:

Go 1.18 發表了泛型 generics,所以可以很輕鬆做到 Parametric polymorphism。下例這個寫法,雖然 Swap 接受各種不同 types ,但保證了的兩個參數必須是同一種 type。

package main

import "fmt"

// Generic function to swap two elements
func Swap[T any](a, b *T) {
*a, *b = *b, *a
}

func main() {
a, b := 5, 10
fmt.Println(a, b) // Outputs: 5 10

Swap[int](&a, &b) // explicit instantiation
Swap(&a, &b) // implicit instantiation

fmt.Println(a, b) // Outputs: 10 5
}

在此之前,我只能用 empty interface 去模擬,而且沒有辦法在 compile 階段判斷 Swap 的對象是否是同一個 type。

package main

import "fmt"

// Generic function to swap two elements
func Swap(ary *[2]any) {
ary[0], ary[1] = ary[1], ary[0]
}

func main() {
ary := [2]any{0, 1}
fmt.Println(ary) // Outputs: 5 10

Swap(&ary)

fmt.Println(ary) // Outputs: 10 5
}

Subtyping:

Go 使用 interface 來限制那些 types 可以被使用,如下例,Animal 是個 interface,必須要會 Sound 與 Eat 這兩個動作;Dog 是個 type,也會 Sound 與 Eat ,所以Dog 可以被當作 Animal 來操作。

package main

import "fmt"

// The Animal interface is defined with two methods
type Animal interface {
Sound() string
Eat()
}

// Dog struct
type Dog struct {
Name string
}

// Dog implements the Animal interface by implementing all its methods
func (d Dog) Sound() string {
return "Woof!"
}

func (d Dog) Eat() {
fmt.Println(d.Name + " eats.")
}

func main() {
d := Dog{Name: "Max"}
fmt.Println(d.Sound())
d.Eat()

// Max can be treated as an Animal because Dog implements Animal
var a Animal = d
fmt.Println(a.Sound())
a.Eat()
}

後記

我認為,在一些過硬的情況下,例如 int 跟 string 都要給出可列印的字串, Parametric polymorphism 沒有辦法真正去解決這個問題,因為不可能靠同一套程式碼去做這件事情,除非把問題丟給更底層,例如 fmt.Sprintf,然後自己當個不沾鍋。

當然在某些 type 根本不重要的情況下,Parametric polymorphism 就很漂亮,例如排序,我只要能實作比大小的method就可以做到排序,根本就不用管底層的 type。在這種情況下,一套 code 可以通吃各種 type,因為type 根本就不重要。

而語言的設計總是在理想(理論)與現實(效能)之間取捨。

--

--