8.5 型タームを使った演算子の指定
type elementで定義されている型を変数やフィールド、戻り値、引数の型に指定するとコンパイル時のエラーとなるので注意
code:go
var zzz Integer = 123 // cannot use type Integer outside a type constraint: interface contains type constraints
type termはデフォルトでは完全に一致しなければいけない
code:go
type Integer interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 | uintptr
}
func divAndRemainderT Integer(num, denom T) (T, T, error) { if denom == 0 {
return 0, 0, errors.New("0で割ることはできません")
}
return num / denom, num % denom, nil
}
func main() {
type MyInt int // intはIntegerに含まれてはいるがMyIntは含まれていない
var myA MyInt = 10
var myB MyInt = 20
fmt.Println(divAndRemainder(myA, myB)) //コンパイル時エラー MyInt does not satisfy Integer (possibly missing ~ for int in Integer)
}
~をつけることで基底型を許容することができる
code:go
type Integer interface {
~int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 | uintptr
}
// 省略
func main() {
type MyInt int
var myA MyInt = 10
var myB MyInt = 20
fmt.Println(divAndRemainder(myA, myB)) // MyIntの基底型はintなので許容 0 10 <nil>
}
type termは事前宣言された型以外にスライスやマップ、配列、チャネル、構造体、関数も指定できる
8.5.1 型推論とジェネリクス
Goでは演算子:=を使うときに型推論をしてくれているが、ジェネリック関数を呼び出す際にも呼び出しを単純にするために型推論をしてくれる
状況によって型推論できない場合はエラーになるので型注釈が必要になる場合もある
code:go
package main
import (
"fmt"
"reflect"
)
type Integer interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
}
return T2(in)
}
func main() {
var a int = 10
b := Convert(a) // 戻す型を推論できないのでエラー in call to Convert, cannot infer T2 (declared at ./prog.go:12:18)
fmt.Println(b, reflect.TypeOf(b)) // 10 int64
}
8.5.2 型要素による定数の制約
下記のようにジェネリック関数でtype elementを使用することで定数に制約をかけることもできる
code:go
package main
import (
"fmt"
)
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
// コンパイル通る
return in + 100 // + 100は全ての整数型で表現可能
}
// コンパイル通らない
return in + 1_000 // + 1_000はint8など一部の整数型で表現不可
}
func main() {
var a int = 10
b := plusOneHundred(a)
fmt.Printf("%T: %v\n", b, b)
}
8.5.3 ジェネリック関数とジェネリックなデータ構造の利用
8章で学んだことを使用して、どんな具象型にも対応できる二本木構造を作る
まず「2つの値を比較してその順序を返す」ジェネリック関数を定義する
code:go
type OrderableFuncT any func(t1, t2 T) int 次にTree構造体と新しいTreeを生成するファクトリの定義をする
code:go
}
val T
}
func NewTreeT any(f OrderableFuncT) *TreeT { f: f,
}
}
TreeのメソッドはNodeのメソッドをラップする
code:go
func (t *TreeT) Add(v T) { t.root = t.root.Add(t.f, v)
}
func (t *TreeT) Contains(v T) bool { return t.root.Contains(t.f, v)
}
Nodeのメソッドで要素の順序付けに使う関数を受け取る
code:go
func (n *NodeT) Add(f OrderableFuncT, v T) *NodeT { if n == nil {
}
switch r := f(v, n.val); {
case r <= -1:
n.left = n.left.Add(f, v)
case r >= 1:
n.right = n.right.Add(f, v)
}
return n
}
func (n *NodeT) Contains(f OrderableFuncT, v T) bool { if n == nil {
return false
}
switch r := f(v, n.val); {
case r <= -1:
return n.left.Contains(f, v)
case r >= 1:
return n.right.Contains(f, v)
}
return true
}
組み込み関数を使用する場合
code:go
func main() {
t1 := NewTree(cmp.Compareint) t1.Add(10)
t1.Add(30)
t1.Add(15)
fmt.Println(t1.Contains(15)) // true
fmt.Println(t1.Contains(40)) // false
}
構造体を使用するパターン1 構造体を受け取る関数を定義
code:go
func main() {
t2 := NewTree(OrderPeople)
t2.Add(Person{"Bob", 30})
t2.Add(Person{"Maria", 35})
t2.Add(Person{"Bob", 50})
fmt.Println(t2.Contains(Person{"Bob", 30})) // true
fmt.Println(t2.Contains(Person{"Fred", 25})) // false
}
type Person struct {
Name string
Age int
}
func OrderPeople(p1, p2 Person) int {
out := cmp.Compare(p1.Name, p2.Name)
if out == 0 {
out = cmp.Compare(p1.Age, p2.Age)
}
return out
}
構造体を使用するパターン2 メソッドを定義
code:go
func main() {
t3 := NewTree(Person.Order)
t3.Add(Person{"Bob", 30})
t3.Add(Person{"Maria", 35})
t3.Add(Person{"Bob", 50})
fmt.Println(t3.Contains(Person{"Bob", 30})) // true
fmt.Println(t3.Contains(Person{"Fred", 25})) // false
}
type Person struct {
Name string
Age int
}
func (p Person) Order(other Person) int {
out := cmp.Compare(p.Name, other.Name)
if out == 0 {
out = cmp.Compare(p.Age, other.Age)
}
return out
}