Разрушение и объяснение параметров типа

Разрушение и объяснение параметров типа

12 июля 2025 г.

Подписывания функций пакета срезов

Аslices.CloneФункция довольно проста: он делает копию кусочка любого типа.

func Clone[S ~[]E, E any](s S) S {
    return append(s[:0:0], s...)
}

Это работает, потому что добавление к ломтике с нулевой емкостью распределяет новый массив поддержки. Функциональное тело становится короче, чем функциональная подпись, которая отчасти потому, что тело короткое, но также потому, что подпись длинная. В этом сообщении мы объясним, почему подпись написана так, как она есть.

Простой клон

Мы начнем с написания простого общегоCloneфункция Это не тот, что вslicesупаковка. Мы хотим взять кусочек любого типа элемента и вернуть новый кусочек.

func Clone1[E any](s []E) []E {
    // body omitted
}

Общая функцияClone1имеет параметр одного типаEПолем Требуется один аргументsкоторый является кусочком типаE, и он возвращает кусок того же типа. Эта подпись проста для всех, кто знаком с дженерами в Go.

Однако есть проблема. Названные типы кусочков не распространены в ходе, но люди используют их.

// MySlice is a slice of strings with a special String method.
type MySlice []string

// String returns the printable version of a MySlice value.
func (s MySlice) String() string {
    return strings.Join(s, "+")
}

Допустим, мы хотим сделать копиюMySliceА затем получите печатную версию, но с струнами в отсортированном порядке.

func PrintSorted(ms MySlice) string {
    c := Clone1(ms)
    slices.Sort(c)
    return c.String() // FAILS TO COMPILE
}

К сожалению, это не работает. Компилятор сообщает об ошибке:

c.String undefined (type []string has no field or method String)

Мы можем увидеть проблему, если мы вручную экземплярClone1заменив параметр типа на аргумент типа.

func InstantiatedClone1(s []string) []string

АGO Правила назначенияПозвольте нам передать значение типаMySliceк параметру типа[]string, так что звонокClone1в порядке. НоClone1вернет значение типа[]string, не значение типаMySliceПолем Тип[]stringнетStringМетод, поэтому компилятор сообщает об ошибке.

Гибкий клон

Чтобы решить эту проблему, мы должны написать версиюCloneЭто возвращает тот же тип, что и его аргумент. Если мы сможем это сделать, то когда мы звонимCloneсо значением типаMySlice, он вернет результат типаMySliceПолем

Мы знаем, что это должно выглядеть примерно так.

func Clone2[S ?](s S) S // INVALID

ЭтотClone2Функция возвращает значение, которое такое же тип, что и его аргумент.

Здесь я написал ограничение как?, но это просто заполнитель. Чтобы сделать эту работу, нам нужно написать ограничение, которое позволит нам написать тело функции. ДляClone1Мы могли бы просто использовать ограничениеanyдля типа элемента. ДляClone2это не сработает: мы хотим, чтобы это требовалоsбыть типом среза.

Поскольку мы знаем, что хотим кусочек, ограничениеSдолжен быть кусочек. Нам не волнует, что такое тип элемента среза, так что давайте просто назовем этоE, как мы делали сClone1Полем

func Clone3[S []E](s S) S // INVALID

Это все еще недействительно, потому что мы не объявилиEПолем Тип аргумент дляEможет быть любой тип, что означает, что он также должен быть самой параметром типа. Поскольку это может быть любой тип, его ограничениеanyПолем

func Clone4[S []E, E any](s S) S

Это приближается, и, по крайней мере, это будет компилироваться, но мы еще не совсем там. Если мы скомпилируем эту версию, мы получим ошибку, когда звонимClone4(ms)Полем

MySlice does not satisfy []string (possibly missing ~ for []string in []string)

Компилятор говорит нам, что мы не можем использовать аргумент типаMySliceДля параметра типаS, потому чтоMySliceне удовлетворяет ограничению[]EПолем Это потому, что[]EВ качестве ограничения только разрешает литерал типа среза, как[]stringПолем Это не позволяет названному типу, какMySliceПолем

Основные ограничения типа

Как намекает сообщение об ошибке, ответ должен добавить~Полем

func Clone5[S ~[]E, E any](s S) S

Повторить, написание параметров и ограничений типа[S []E, E any]означает, что аргумент типа дляSможет быть любой неназванный тип среза, но это не может быть названным типом, определяемым как литерал с ломтиком. Письмо[S ~[]E, E any], с~, означает, что аргумент типа дляSможет быть любой тип, основной тип которого является тип среза.

Для любого названного типаtype T1 T2основной типT1является основным типомT2Полем Базовый тип предварительного типа, подобногоintили тип буквального подобного[]stringэто просто сам тип. Для точных деталей,Смотрите языковую спецификациюПолем В нашем примере основной типMySliceявляется[]stringПолем

Поскольку основной типMySliceэто ломтик, мы можем передать аргумент типаMySliceкClone5Полем Как вы могли заметить, подписьClone5такой же, как и подписьslices.CloneПолем Мы наконец добрались до того места, где хотим быть.

Прежде чем мы продолжим, давайте обсудим, почему синтаксис GO требует~Полем Может показаться, что мы всегда хотели бы разрешить прохождениеMySlice, так почему бы не сделать это по умолчанию? Или, если нам нужно поддерживать точное соответствие, почему бы не перевернуть вещи, чтобы ограничение[]Eразрешает названный тип, в то время как, скажем, ограничение, скажем,=[]E, только разрешает литералы типа среза?

Чтобы объяснить это, давайте сначала заметим, что список параметров типа, как[T ~MySlice]не имеет смысла. Это потому, чтоMySliceне является основным типом любого другого типа. Например, если у нас есть определение, подобноеtype MySlice2 MySlice, основной типMySlice2является[]string, нетMySliceПолем

Так тоже[T ~MySlice]не допускает никаких типов вообще, или это будет таким же, как[T MySlice]и только совпадатьMySliceПолем В любом случае,[T ~MySlice]бесполезно. Чтобы избежать этой путаницы, язык запрещает[T ~MySlice]и компилятор производит ошибку, подобную

invalid use of ~ (underlying type of MySlice is []string)

Если GO не потребует тилде, так что это[S []E]соответствовал бы любому типу, основной тип которого[]E, тогда нам придется определить значение[S MySlice]Полем

Мы могли бы запретить[S MySlice], или мы могли бы сказать, что[S MySlice]только совпадаютMySlice, но любой подход сталкивается с неприятностями с предшественниками типов. Предопределенный тип, какint, является его собственным основным типом. Мы хотим позволить людям иметь возможность писать ограничения, которые принимают любой аргумент, основной тип которогоintПолем На языке сегодня они могут сделать это, написав[T ~int]Полем Если нам не требуется тильда, нам все равно понадобится способ сказать «любой тип, чей основной типint”. Естественный способ сказать, что это будет[T int]Полем Это будет означать, что[T MySlice]и[T int]будет вести себя по -другому, хотя они выглядят очень похожими.

Возможно, мы могли бы сказать, что[S MySlice]соответствует любому типу, основной тип которого является основной типMySlice, но это делает[S MySlice]ненужный и запутанный.

Мы думаем, что лучше требовать~И будьте очень ясны, когда мы сопоставляем базовый тип, а не сам тип.

Тип вывод

Теперь, когда мы объяснили подписьslices.Clone, давайте посмотрим, как на самом деле использоватьslices.Cloneупрощается с помощью типового вывода. Помните, подписьCloneявляется

func Clone[S ~[]E, E any](s S) S

Звонокslices.Cloneпередаст ломтик параметраsПолем Простой вывод типа позволит компилятору сделать вывод, что аргумент типа для параметра типаSтип среза, передаваемого вCloneПолем Тип вывод является тогда достаточно мощным, чтобы увидеть, что аргумент типа дляEЯвляется ли тип элемента аргумента, передаваемогоSПолем

Это означает, что мы можем написать

    c := Clone(ms)

без необходимости писать

    c := Clone[MySlice, string](ms)

Если мы обратимся кCloneНе назвав это, мы должны указать аргумент типа дляS, поскольку у компилятора нет ничего, что он может использовать, чтобы сделать это. К счастью, в этом случае вывод типа способен вывести аргумент типа дляEот аргументаS, и нам не нужно указывать это отдельно.

То есть мы можем написать

    myClone := Clone[MySlice]

без необходимости писать

    myClone := Clone[MySlice, string]

Деконструкция параметров типа

Общая техника, которую мы использовали здесь, в которой мы определяем один параметр типаSИспользование другого параметра типаE, это способ деконструировать типы в общих подписях функции. Деконструируя тип, мы можем назвать и ограничить все аспекты типа.

Например, вот подпись дляmaps.CloneПолем

func Clone[M ~map[K]V, K comparable, V any](m M) M

Так же, как сslices.Clone, мы используем параметр типа для типа параметраm, а затем деконструируйте тип, используя два других параметров типаKиVПолем

Вmaps.CloneМы ограничиваемKбыть сопоставимым, как требуется для типа ключа карты. Мы можем ограничить типы компонентов, как нам нравится.

func WithStrings[S ~[]E, E interface { String() string }](s S) (S, []string)

Это говорит о том, что аргументWithStringsДолжен быть тип среза, для которого тип элемента имеетStringметод

Поскольку все типы GO могут быть созданы из типов компонентов, мы всегда можем использовать параметры типа для деконструкции этих типов и ограничить их, как нам нравится.


Ян Лэнс Тейлор

ФотоРобин Джонатан ДойчнаНеспособный

Эта статья доступна наБлог GOПод CC по лицензии 4,0.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE