Interface type literal
Tags: go
Published: 2021-09-26 05:09:00

เวลาเราต้องการใช้ interface ส่วนใหญ่เราจะสร้าง type ชื่อใหม่สำหรับ inteface แล้วค่อยเอา type ชื่อใหม่นั้นไปใช้ ประกาศตัวแปร หรือกำหนด parameter ของ function/method แต่จริงๆเราสามารถใช้งาน interface ได้เลยโดยไม่ต้องสร้าง type ใหม่ได้เช่นกัน

## Type Literal
จาก spec ของ Go https://golang.org/ref/spec#Types บอกเอาไว้เกี่ยวกับ Type Literal เอาไว้ว่า

> A type may be denoted by a type name, if it has one, or specified using a type literal, which composes a type from existing types.

> TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType.

จากที่เห็นในลิสต์นี้ ทั้งหมดนี่คือ type ที่เราเอามาใช้งานแบบ type literal ได้ ซึ่งก็รวมถึง InterfaceType ด้วยนั่นเอง

## ประกาศตัวแปรโดยใช้ Interface type literal
เวลาประกาศตัวแปรของ interface type literal เราก็แค่ยก interface block แบบที่เราเคยใช้กับตอน ประกาศ type นั่นล่ะ มาใช้ได้เลย เช่น

```go
var finder interface {
    Find(keyword string) []result
}
```

เท่านี้ตัวแปร finder ก็จะเป็นตัวแปรของ type interface ที่มี 1 method คือ `Finder(keydord string) []result` แล้ว

หรือมีจุดอื่นที่เราต้องการใช้งาน interface type เช่นตอนทำ type assertion เราก็สามารถยก type literal ไปใช้ได้เช่นกันโดยไม่ต้องสร้าง type ชื่อใหม่ เช่น

```go
finder, ok := f.(interface { Finder(string) []result })
```

## ตัวอย่างจาก standard package `errors`
package `errors` https://pkg.go.dev/errors นั้นมี concept ใหม่คือเรื่อง `Unwrap` error ซึ่ง document เขียนเอาไว้ว่า

> The `Unwrap`, `Is` and `As` functions work on errors that may wrap other errors. An error wraps another error if its type has the method `Unwrap() error`

คือ `Unwrap`, `Is` และ `As` functions ใน package errors นั้นทำงานกับ error type ที่มี method `Unwrap() error`

บอกแบบนี้นั่นก็คือ type ที่ implements method `Unwrap() error` นั่นละ แต่ว่าถ้าเราดู type ที่อยู่ใน document ของ `errors` จะพบว่าเราไม่เจอ interface type สำหรับ `Unwrap() error` method เลย

แบบนี้แล้ว function `Unwrap`, `Is` และ `As` มันจัดยังไงนะ ลองเข้าไปดูโค้ดของสามฟังก์ชันนี้กัน


เริ่มที่ `Unwrap`

```go
func Unwrap(err error) error {
	u, ok := err.(interface {
		Unwrap() error
	})
	if !ok {
		return nil
	}
	return u.Unwrap()
}
```

จะเห็นว่า ใช้ type assertion แต่ว่าตรงที่อยู่ใน `()` หลัง `err.` นั้นไม่ใช่ชื่อ type โดยตรง แต่เป็นการใช้ interface type literal นั่นเอง


ถัดไปดูที่ `Is`

```go
func Is(err, target error) bool {
	if target == nil {
		return err == target
	}

	isComparable := reflectlite.TypeOf(target).Comparable()
	for {
		if isComparable && err == target {
			return true
		}
		if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
			return true
		}

		if err = Unwrap(err); err == nil {
			return false
		}
	}
}
```

จะเห็นว่าก็มีจุดที่ใช้ type assertion พร้อมกับใช้ interface type literal เช่นกัน แต่เป็นการเช็คว่า type implements method `Is(error) bool` หรือเปล่า

ต่อไปก็ดูที่ `As`

```go
func As(err error, target interface{}) bool {
	if target == nil {
		panic("errors: target cannot be nil")
	}
	val := reflectlite.ValueOf(target)
	typ := val.Type()
	if typ.Kind() != reflectlite.Ptr || val.IsNil() {
		panic("errors: target must be a non-nil pointer")
	}
	targetType := typ.Elem()
	if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
		panic("errors: *target must be interface or implement error")
	}
	for err != nil {
		if reflectlite.TypeOf(err).AssignableTo(targetType) {
			val.Elem().Set(reflectlite.ValueOf(err))
			return true
		}
		if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
			return true
		}
		err = Unwrap(err)
	}
	return false
}
```

ก็เหมือนกับสองอันก่อนหน้านี้แต่คราวนี้คือเช็คว่า type implements method `As(interface{}) bool` หรือเปล่า นั่นเอง


## ทำไมเขาถึงเลือกใช้ท่านี้กันนะ 🤔

คิดว่าอาจจะเป็นเพราะ interface ที่จะใช้งานนั้นมีแค่ method เดียว แล้วก็ไม่ได้ใช้ที่อื่นเลยยกเว้นแค่ในตัวฟังก์ชันเอง

กับสิ่งที่ทำให้ทำแบบนี้ได้ก็เพราะ Go interface นั้นคน implements ไม่จำเป็นต้องรู้ type ของ interface ของแค่มี methods ตรงตาม spec แค่นั้นเอง

อ้าวแล้วถ้าคน implements ไม่ได้ implement method ที่ต้องการไว้ล่ะ

ก็เป็นหน้าที่ของฝั่งคนใช้ interface ในกรณีนี้คือ 3 ฟังก์ชันนี้ ต้องเขียน logic จัดการเอาไว้ว่าถ้า implement จะทำยังไง ถ้าไม่ implement จะทำยังไง ซึ่งก็ใช้ type assertion นั่นเอง

อาจจะดูแปลกๆเพราะคัดกับภาษาที่คน implement ต้องระบุชัดเจนว่าจะ implement interface ไหนบ้าง เช่น (Java, C#)

เพราะหลักการของ Go นั้น คนที่ต้องการใช้งาน interface ต้องเป็นคนระบุ spec เอง ในเคสนี้คือฟังก์ชัน `Unwrap`, `Is` และ `As` ทั้ง 3 ฟังก์ชันนี้ใช้ Interface เพื่อให้การทำงานของตัวเองยืดหยุ่นพอที่จะทำงานกับหลายๆ type ได้ ขอแค่ type นั้น implements method ตามที่ต้องการ