понедельник, 11 февраля 2019 г.

Go FAQ: преобразования срезов

Могу ли я преобразовать []T в []interface{}?

Не напрямую. Это запрещено в спецификации языка, потому что два типа не имеют одинакового представления в памяти. Необходимо скопировать элементы индивидуально в целевой срез. В следующем примере преобразуется срез int в срез interface{}:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

Могу ли я преобразовать []T1 в []T2, если T1 и T2 имеют одинаковый базовый тип?

Последняя строка этого примера кода не компилируется.

type T1 int
type T2 int
var t1 T1
var x = T2(t1) // OK
var st1 []T1
var sx = ([]T2)(st1) // не OK, не компилируется

В Go типы тесно связаны с методами, так как каждый именованный тип имеет (возможно, пустой) набор методов. Общее правило заключается в том, что вы можете изменить имя преобразованного типа (и, следовательно, возможно, изменить свой набор методов), но вы не можете изменить имя (и набор методов) элементов составного типа. Go требует, чтобы вы были явными о преобразованиях типов.


Читайте также:


Go FAQ: Почему тип T не удовлетворяет интерфейсу Equal?

Рассмотрим простой интерфейс для представления объекта, который может сравнивать себя с другим значением:

type Equaler interface {
    Equal(Equaler) bool
}

и этот тип, T:

type T int

// не удовлетворяет Equaler
func (t T) Equal(u T) bool { return t == u } 

В отличие от аналогичной ситуации в некоторых системах полиморфного типа, T не реализует Equaler. Тип аргумента T.Equal - это T, но буквально не обязательный тип Equaler.

В Go система типов не поддерживает аргумент Equal; это ответственность программиста, как иллюстрируется типом T2, который реализует Equaler:

type T2 int

// удовлетворяет Equaler
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }  

Даже это не похоже на другие системы типов, потому что в Go любой тип, который удовлетворяет Equaler, может быть передан как аргумент T2.Equal, и во время выполнения мы должны проверить, что аргумент имеет тип T2. Некоторые языки предоставляют такую ​​гарантию во время компиляции.

Связанный пример идет другим путем:

type Opener interface {
   Open() Reader
}

func (t T3) Open() *os.File

В Go T3 не удовлетворяет Opener, хотя может на другом языке.

Хотя это правда, что система типов Go делает меньше для программиста в таких случаях, отсутствие подтипов делает правила об удовлетворении интерфейса очень легко определяемыми: имена функций и сигнатуры именно таковые из интерфейса? Правило Go также легко реализовать эффективным образом. Создатели Go считают, что эти преимущества компенсируют отсутствие автоматического продвижения типа.


Читайте также:


суббота, 9 февраля 2019 г.

Go FAQ: Как я могу гарантировать, что мой тип удовлетворяет интерфейсу?

Вы можете попросить компилятор проверить, что тип T реализует интерфейс I, пытаясь присвоить, используя нулевое значение для T или указатель на T, в зависимости от ситуации:

type T struct{}
var _ I = T{}       // Проверяем, что T реализует I.
var _ I = (*T)(nil) // Проверяем, что *T реализует I.

Если T (или *T, соответственно) не реализует I, ошибка будет обнаружена во время компиляции.

Если вы хотите, чтобы пользователи интерфейса явно заявили, что они реализуют его, вы можете добавить метод с описательным именем к набору методов интерфейса. Например:

type Fooer interface {
    Foo()
    ImplementsFooer()
}

Затем тип должен реализовать метод ImplementsFooer, чтобы быть Fooer, четко документируя факт и анонсируя его в godoc.

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

Большая часть кода не использует такие ограничения, так как они ограничивают полезность идеи интерфейса. Иногда, однако, они необходимы для устранения неясностей среди похожих интерфейсов.


Читайте также:


Go FAQ: Почему Go не поддерживает перегрузку методов и операторов?

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

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


Читайте также:


Go FAQ: Почему в Go нет объявлений "implements"?

Тип в Go удовлетворяет интерфейсу, реализуя методы этого интерфейса, ничего более. Это свойство позволяет определять и использовать интерфейсы без необходимости изменять существующий код. Это позволяет своего рода структурную типизацию, которая способствует разделению проблем и улучшает повторное использование кода, и облегчает построение кода на паттернах (шаблонах), которые появляются по мере развития кода. Семантика интерфейсов является одной из основных причин гибкости Go и его легковесности.


Читайте также:


Go FAQ: Почему в Go нет наследования типов?

Объектно-ориентированное программирование, по крайней мере на самых известных языках, включает в себя слишком много обсуждения отношений между типами, отношений, которые часто могут быть получены автоматически. Go использует другой подход.

Вместо того, чтобы требовать от программиста заранее объявить, что два типы связаны, в Go тип автоматически удовлетворяет любому интерфейсу - это определяет подмножество его методов. Помимо уменьшения количества кода, у этого подхода есть реальные преимущества. Типы могут удовлетворить много интерфейсов одновременно, без сложностей традиционного множественного наследования. Интерфейсы могут быть очень легкими - интерфейсы с одним методом или даже без метода могут выразить полезную концепцию. Интерфейсы могут быть добавлены после того, как придет новая идея или для тестирования - без указания оригинальных типов. Поскольку нет явных отношений между типами и интерфейсами, нет иерархии типов для управления ими или обсуждения их.

Можно использовать эти идеи для создания чего-то аналогичного безопасных для типов Unix pipes. Например, посмотрите, как fmt.Fprintf позволяет форматировать печать на любой вывод, не только файл, или как пакет bufio может быть полностью отделен от файлового ввода-вывода, или как пакеты image генерируют сжатые файлы изображений. Все эти идеи проистекают из единого интерфейса (io.Writer), представляющего один метод (Write). И это только малая часть. Интерфейсы Go оказывают глубокое влияние на структуру программ.

Требуется некоторое привыкание, но этот неявный стиль зависимости типа - одна из самых продуктивных вещей в Go.


Читайте также:


Go FAQ: Является ли Go объектно-ориентированным языком?

И да и нет. Хотя Go имеет типы и методы и позволяет объектно-ориентированный стиль программирования, в Go отсутствует иерархия типов. Концепция "интерфейса" в Go обеспечивает другой подход, который легко использовать. Есть также способы встраивания типов в другие типы, чтобы обеспечить что-то аналогичное - но не идентичное - подклассам. Более того, методы в Go более общие, чем в C++ или Java: они могут быть определены для любого вида данных, даже встроенных типов, таких как как простые, "unboxed" (без контейнера) целые числа (integers). Они не ограничены структурами (классами).

Кроме того, отсутствие иерархии типов делает "объекты" в Go намного более легкими, чем в таких языках, как C++ или Java.


Читайте также: