🤗
Underlying Typeを使ってGoのスライス操作をちょっとだけ便利にする
go1.18でGenericsが導入されてから、samber/lo
がスライスの操作に便利でよく使っています。
しかし、不満もあります 😢
以下のようにスライスの型にメソッド定義していたとしましょう。
type UserName string
type User struct {
Name UserName
}
type Users []*User
func (us Users) JoinNames(sep string) string {
return strings.Join(us.Names(), sep)
}
func (us Users) Names() []string {
return lo.Map(us, func(item *User, _ int) string {
return string(item.Name)
})
}
lo.Filter
で対象のユーザーを絞り込んでから、
Usersに定義したメソッドを使って名前を表示してみます。
func main() {
users := Users{{Name: "Tom"}, {Name: "Bob"}, {Name: "Alice"}, {Name: "Bem"}}
fmt.Println(lo.Filter(users, func(user *User, _ int) bool {
return strings.HasSuffix(string(user.Name), "m")
}).JoinNames(", "))
}
よーし、めっちゃcoolに書けたやん?と思いきや
Nooooooo 😱😱😱
lo.Filter(users, func(user *User, _ int) bool {…}).JoinNames undefined (type []*User has no field or method JoinNames)
そうです。
lo.Filterの戻り値は[]*User
になるのでUsers
にキャストしてあげなければUsers
に定義したメソッドは使えません。
func main() {
users := Users{{Name: "Tom"}, {Name: "Bob"}, {Name: "Alice"}, {Name: "Bem"}}
fmt.Println(Users(lo.Filter(users, func(user *User, _ int) bool {
return strings.HasSuffix(string(user.Name), "m")
})).JoinNames(", "))
// "Tom, Bem"
}
本題です
Underlying Typeを使って戻り値でUsers
を受け取れるFilterを作ってみましょう。
これでキャストが不要になるはずです。
func Filter[T any, L ~[]T](list L, fn func(item T) bool) L {
result := make(L, 0, len(list))
for i := range list {
if fn(list[i]) {
result = append(result, list[i])
}
}
return result
}
func main() {
users := Users{{Name: "Tom"}, {Name: "Bob"}, {Name: "Alice"}, {Name: "Bem"}}
fmt.Println(Filter(users, func(user *User) bool {
return strings.HasSuffix(string(user.Name), "m")
}).JoinNames(", "))
// "Tom, Bem"
}
無事にキャストなしでUsersのメソッドを使えるようになりました 🤗🤗🤗
ちょっと応用してstring
をUnderlying Typeに持っていれば受けられる
strings.Join
相当の関数を作ってみます。
func JoinString[S ~string, L ~[]S](list L, sep string) string {
sb := new(strings.Builder)
for i := range list {
if i > 0 {
sb.WriteString(sep)
}
sb.WriteString(string(list[i]))
}
return sb.String()
}
type UserName string
type UserNames []UserName
type UserNames2 []string
func main() {
fmt.Println(JoinString([]string{"Tom", "Bob", "Alice"}, " | "))
// "Tom | Bob | Alice"
fmt.Println(JoinString([]UserName{"Tom", "Bob", "Alice"}, " | "))
// "Tom | Bob | Alice"
fmt.Println(JoinString(UserNames{"Tom", "Bob", "Alice"}, " | "))
// "Tom | Bob | Alice"
fmt.Println(JoinString(UserNames2{"Tom", "Bob", "Alice"}, " | "))
// "Tom | Bob | Alice"
}
めっちゃ便利🤩🤩🤩
動かせるコードはplaygroundに置いておきました。
Discussion