На прошлой неделе команда Go опубликовала страницу с подробными проектами спецификаций для обобщений Go 2 и инструментов обработки ошибок. Предложение одновременно хорошо продуманное и смелое, поскольку одна из основных идей - ввести систему контрактов, аналогичную концепциям C ++. Контракты, на языке Go, представляют собой (возможно, синтаксически ограниченные) именованные функции, параметризованные как по значению, так и по его типу, которые используются компилятором для обеспечения того, чтобы некоторый контракт удерживался указанными типами. В контрактах замечательно то, что процедура проверки контрактов очень проста - это всего лишь тело функции, компиляторы проверяют типы, компилируют, а затем отбрасывают свое тело и все.
Но за простоту компилятора часто приходится платить пользователю. Этот пост представляет собой попытку проанализировать предложение и собрать все заметки, которые я сделал во время чтения документа. В разделе Синтаксис дается несколько замечаний по предлагаемому синтаксису, а в разделе Семантика описывается… ну, семантика.
Синтаксис
Одна из самых важных маленьких вещей в Go - это минималистичный и чистый синтаксис. Как правило, предложение остается неизменным, но есть несколько моментов, которые в некоторых местах делают слишком много.
Объявления параметров типа функции
Поскольку методы, похоже, не имеют параметров типа, для автономных функций они могут быть объявлены вместо аргумента приемника метода:
func (type T) Print(slice []T) {
for _, v in range slice {
fmt.Printf("%v", v)
}
}
Это одновременно упрощает форму (type ...)(parameters ...) и более логично с точки зрения того, что параметры типа и обычные параметры должны быть четко разделены. Проблема в том, что такой синтаксис немного неоднозначен визуально, но все же он намного проще с точки зрения записи, чем (type ...)(parameters ...).
Контракт как другой вид типа
Чтобы избежать сложностей совместимости с Go 1 с созданием контракта ключевым словом, контакты можно рассматривать как еще один вид объявления типа, т. Е. Использовать синтаксис.
type Convertible(_ To, f From) contract {}
Однако это может создать семантическую проблему, поскольку Go обычно придерживается прямого сопоставления между типами и представления значений в памяти: структуры сопоставляются с последовательностями именованных полей, интерфейсы сопоставляются со значениями с двойным указателем или парами (val, vtable), в то время как контракты не сопоставляются ни к чему, поскольку они являются исключительно конструкциями времени компиляции.
Семантика
Контракты или нет?
Заявление об этом так далеко в посте может быть странным, но одно из первых замечаний, которые я сделал по поводу предложения контракта, заключается в том, что мне на самом деле не нравится идея иметь функциональные тела, которые не являются функциональными телами, что может вводить в заблуждение. Введение ограничений для контрактных тел требует расширения грамматики Go и отсутствие каких-либо ограничений может вызвать травмы глаз при чтении контрактных тел с _4 _ / _ 5 _ / _ 6_ утверждениями. Разрешение только операторов присваивания и выражений в теле контракта не помешает ему содержать какую-либо логику, помимо простого объявления набора разрешенных операций, что по-прежнему может вводить в заблуждение читателя.
Рассмотрим следующие конструкции:
type Pair struct {
first, second int
}
type Cons struct {
head, tail int
}
Просто взглянув на эти объявления, мы можем сказать, что они структурно идентичны и, согласно спецификациям Go, даже имеют одинаковое представление в памяти. Другими словами, эти две структуры изоморфны, и даже если мы никогда не работали с языком Lisp и не видели структуру Cons раньше, мы можем легко вывести, как она себя ведет, поскольку она изоморфна Pair, a простая структура всем известна.
Аналогичным образом рассмотрим эти два интерфейса:
type Index interface {
Clear(i int)
Set(i int, value string)
}
type StringMap interface {
Insert(k string, value interface{})
Remove(k string)
}
Несмотря на то, что это намеренно сложный пример, и операции в этих интерфейсах совершенно разные, они имеют одинаковую структуру подписи и, таким образом, могут считаться изоморфными и пониматься как выполняющие аналогичные задачи по включению и изменению некоторого значения в заданном ключе.
Теперь рассмотрим эти два контракта:
contract strseq1(x T) {
[]byte(x)
T([]byte{})
len(x)
}
contract strseq2(a T) {
var c int = len(a)
var bs []byte = []byte(a)
T(bs)
}
Они объявляют один и тот же набор операций, но для того, чтобы сказать, что вы должны разместить их рядом и немного подумать. Нелегко неформально проверить, что эти два контракта изоморфны, и чрезвычайно сложно сделать это формально (см. Изоморфизм графов). Другими словами, структуры и интерфейсы декларативны и просты для понимания, в то время как контракты являются обязательными, и чтобы понять контракт, вы должны следовать их утверждениям. Тем не менее, все, что они делают, - это просто объявляют набор операций над некоторыми типами. В Go уже есть интерфейсы для этого, поэтому более простой подход - добавить методы операторов, придумать способ ограничения перегрузки и просто использовать интерфейсы.
Контракты от интерфейсов
Предполагая, что контракты здесь, чтобы остаться, мы можем пойти на компромисс между контрактами и интерфейсами, разрешив использовать имена интерфейсов в качестве имен контрактов в объявлениях, чтобы повторно использовать тривиальные объявления контрактов, т.е. каждое объявление интерфейса должно генерировать эквивалентный контракт, как показано ниже:
type ReadWrite interface {
Read() string
Write(string)
}
contract ReadWrite(x T) {
val s string = x.Read()
x.Write(s)
}
Это позволит использовать интерфейсы для большинства случаев, когда контракты описывают только набор методов и прибегают к контрактам только в сложных случаях, таких как преобразования и операторы. Важным следствием такого соответствия является стандартная библиотека контрактов, автоматически получаемая из стандартных интерфейсов.
Вывод
Текущее предложение дженериков Go очень хорошо продумано и в значительной степени позволяет избежать всех проблем с системами параметрического полиморфизма в таких языках, как C ++ и Java. Контракты - очень интересная концепция (каламбур), но у них есть некоторые семантические недостатки, которые еще не решены, и, возможно, никогда не будет простого решения. Имея это в виду, если есть какие-либо изменения, чтобы заменить их чистыми и чистыми старыми интерфейсами, мы должны сделать все возможное, чтобы попытаться избежать добавления их в язык в конце, поскольку после добавления таких функций очень трудно избавиться .