go

Interface type literal

iporsut

Weerasak Chongnguluam

Posted on September 26, 2021

Interface type literal

เวลาเราต้องการใช้ 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 นั่นล่ะ มาใช้ได้เลย เช่น

var finder interface {
    Find(keyword string) []result
}
Enter fullscreen mode Exit fullscreen mode

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

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

finder, ok := f.(interface { Finder(string) []result })
Enter fullscreen mode Exit fullscreen mode

ตัวอย่างจาก 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

func Unwrap(err error) error {
    u, ok := err.(interface {
        Unwrap() error
    })
    if !ok {
        return nil
    }
    return u.Unwrap()
}
Enter fullscreen mode Exit fullscreen mode

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

ถัดไปดูที่ Is

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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
}
Enter fullscreen mode Exit fullscreen mode

ก็เหมือนกับสองอันก่อนหน้านี้แต่คราวนี้คือเช็คว่า 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 ตามที่ต้องการ

💖 💪 🙅 🚩
iporsut
Weerasak Chongnguluam

Posted on September 26, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related