Снова поговорим про
[T Constraint[T]], ведь оно работает и для объявлений обобщённых типов. Это удобно для коллекций, которые сортируют доменные типы по их собственному правилу сравнения.Стандартный
constraints.Ordered покрывает числа и строки, но не богатые доменные типы вроде Priority, SemanticVersion или Money. Для них порядок задаётся своей логикой, а не оператором <. Обычно это решают через sort.Slice с замыканием на каждый вызов или через хранение значений как interface{} с приведением при каждом сравнении. Первый вариант рассыпает логику сравнения по коду, второй теряет типобезопасность и добавляет работу рантайму.➡️ Как это работает
Ограничение требует, чтобы тип умел сравнивать себя с себе подобным:
type Ordered[T any] interface {
Less(other T) bool
}
// T обязан реализовать Less(T) bool.
type SortedSlice[T Ordered[T]] struct {
items []T
}
func (s *SortedSlice[T]) Insert(v T) {
i := sort.Search(len(s.items), func(i int) bool {
return !s.items[i].Less(v)
})
s.items = append(s.items, v)
copy(s.items[i+1:], s.items[i:])
s.items[i] = v
}Доменный тип реализует
Less для себя:type Priority int
func (p Priority) Less(other Priority) bool {
return p < other
}
set := &SortedSlice[Priority]{}
set.Insert(High)
set.Insert(Low)
SortedSlice[Priority] на этапе компиляции знает, что хранит именно Priority. Вызов .Less прямой, без интерфейсной прослойки в рантайме. Если попытаться вставить тип, который не реализует Ordered[T] для себя, компилятор отклонит его прямо на месте вызова, а не оставит падать под нагрузкой.Паттерн переносит правило сравнения внутрь самого типа и даёт коллекцию, которая держит порядок для любого доменного значения. Логика
Less живёт в одном месте, типобезопасность сохраняется, приведений нет. Для приоритетных очередей, версий и денежных сумм это чище, чем замыкания на каждый вызов сортировки.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction
