Как стать автором
Обновить

Комментарии 58

То есть даже Go 2.0 ждать не придётся? Чудеса...

Разработчики никогда не позиционировали "Go 2.0" как мажорный релиз, в котором всё сломают и перепишут. По крайней мере, в блогах они пишут, что не стоит на это расчитывать в ближайшем будущем.
Это скорее дополнение к стандартному proposal evaluation process (который не позволяет вносить изменения, значительно меняющие язык), позволяющее, собственно, предлагать изменения вроде дженериков, которые будут значительно менять язык

Само собой.


Но, как я всегда считал, Go 2.0 может сломать Go 1.x Compatibility Promise (который, конечно, всё равно нарушают в некоторой степени)


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

После введения дженериков ряд стандартных пакетов (из очевидного — container) хотелось бы видеть переделанными с пустых интерфейсов на дженерики, что, конечно, сломает обратную совместимость.
Самое то для go2 :)

НЛО прилетело и опубликовало эту надпись здесь
И в этом примере я не вижу дженериков. Тоже самое можно же интерфейсами написать.

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

НЛО прилетело и опубликовало эту надпись здесь

Для го дженерики намного менее нужны, чем в других языках. Поэтому их и добавляют только через 10 лет

Нужно понимать, что любой язык вполне живёт без дженериков. К примеру, в JS вообще нету типизации и ничего.


Отсутствие дженериков в Гоу приводит только к одному — динамической типизации в кучи моментов. А динамическая типизация в большинстве случаев — это объективный недостаток.


Но да, вполне можно программировать на динамических языках. Только зачем?

В Go не может быть динамической типизации, потому что это статически типизированный язык. Я понятия не имею, что вы имели под этим в виду, но пустой интерфейс — это скорее аналог void * с RTTI заголовком. Вы же не назовёте C — языком с динамической типизацией из-за того что там есть void *. Непустые интерфейсы же похожи на VMT. В С++ почему-то от наследования не отказываются из-за этого


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


Класс задач, в которых они нужны, не такой большой. Дженерики могут сделать решение некоторых задач удобнее и упростить написание библиотек, но нет никакой кучи моментов, где без них не обойтись

Ну interface — это тот же object из соседей, ну или any из тайпскрипта. Т.Е. любой тип. Простите, но типизацию, где мы что-то типизируем как "любой тип" мне сложно назвать статической.


Да, если вы будете использовать в си указатель, который позволяет указывать на любой тип, я скажу, что конкретно в этом месте у вас динамическая типизация, по определению)


То, что вы не понимаете, где массово используются дженерики не означает, что они не нужны)


А на Гоу я писал. Отвратительный язык. И не только из-за дженериков. К примеру из-за уродских динамических тегов.

Ну так не пишите, если не нравится. Зачем вы приходите в каждый пост про Go говорить, какой он отвратительный?

Хочу и говорю. Это называется "общаться". Не нравится? Не читайте и не отвечайте.


Тем более я не могу промолчать если кто-то открыто заблуждается.

void* — вполне себе динамическая типизация, именно потому что это "что-то с чем-то" и вместо ошибой типов в компайл тайм мы имеем рантайм ошибка "Ожидалось что придет объект типа Х а пришел типа У". Динамическая типизация — термин очень обширный и каждый понимает свое, но обычно именно что-то в этом духе и имеется в виду

По вашему, Java — тоже динамически типизированный язык? Ведь Object для объектов Java играет точно ту же роль, что и interface{} в Go.

Но нет, никто не называет Java динамическим: все понимают, что Object — полиморфизм, а не динамическая типизация.

Но почему-то не все понимают, что interface{} — такой же полиморфизм, обобщённый на все типы языка, а не только на объекты.

Динамическая типизация — это когда язык сам умеет приводить значения переменных динамического типа к реальному типу. Если бы можно было написать:
a, b := interface{}(3), interface{}(7)
fmt.Println(a + b)
— это была бы динамическая типизация.

Но, нет: всё, что позволяет Go — только копировать значение interface{}. Чтобы использовать значение, необходимо явное ручное приведение interface{} к его реальному типу. Никакой динамической типизации тут и близко нет.

Ещё раз. Я не говорю, что весь язык динамический, но конкретно у этой механики — динамическая природа.


это когда язык сам умеет приводить значения переменных динамического типа к реальному типу

Ну уж нет, это слабая типизация, вот даже цитата из википедии вам:


характерными атрибутами [слабой системы типов] являются понятия приведения типов

Я напомню, что Питон не приводит никакие типы автоматически, но в нём всё ещё динамическая типизация.


И да, если на Джава писать все переменные в виде object, то это будет написание кода в динамическом стиле. Ты можешь в любой метод передать всё, что угодно, а уже локально в рантайме, а не компайл тайме будет проверяться тип.


Хочу заметить, что в том же c# использование object считается дурным тоном и явно указывает на то, что у вас проблемы с типизацией.

class Foo {
    public void Test() {}
}

/**
 * Классический строго-типизированный код
 */
public class Static
{

    public void Main()
    {
        Method(new Foo());
    }

    private void Method (Foo foo) {
        foo.Test();
    }

}

Далее — динамический код, при передаче в метод непривального аргумента, например Method(new Bar()); ошибка будет не в компайл тайме, как в статически типизированных языках, а в рантайме, как в динамически типизированных языках и да, это динамически типизированный код на C#


public class Dynamic
{
    public void Main()
    {
        Method(new Foo());
        Method(new Bar()); // we still can
    }

    private void Method (object foo) {
        ((Foo) foo).Test();
    }
}
Хочу заметить, что в том же c# использование object считается дурным тоном и явно указывает на то, что у вас проблемы с типизацией.

Сомневаюсь, что в Java писать все переменные в виде object не считается дурным тоном, и в C использование void * везде не считается дурным тоном. В Go точно так же использование interface{} никто не рекомендует использовать там, где это не нужно.


Нужно понимать, что пустой интерфейс — это просто вырожденный случай сущности "интерфейс", которая по определению является средством описания набора методов, которые могут быть вызваны у объекта, реализующего конкретный интерфейс.
Это не способ описания типов, это способ описания поведения.


Ну и что, скажете вы? Какая разница, если его всё равно используют как костыль, потому что нет иных инструментов?
А разница в том, что interface{} нельзя никак использовать, потому что он не реализует никаких методов. Чтобы что-то с ним сделать, его нужно принудительно привести к статическому типу.


Если вы захотите, например, использовать std::set в С++ для каких-то собственных классов, вам нужно будет в этот класс добавить хотя бы operator<, несмотря на то, что std::set является шаблонным. В Go, если вы хотите использовать "sort", вам точно так же нужно, чтобы объекты реализовывали sort.Interface.
Разница с языками, у которых есть чёткие дженерики только в том, что для стандартных типов вроде int, string и т.д. такие операции нужно явно делать вручную.


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

Чтобы что-то с ним сделать, его нужно принудительно привести к статическому типу.

Вот именно!


И при ошибке типа будет что?
Правильно, ошибка в рантайме.


А ошибка в рантайме при неправильном типе у нас в каких языках?
Правильно, динамических!


А нафига такую хрень вообще использовать?
Правильно, потому что нету дженериков.


Я рад, что мы с вами сошлись.

Я приведу один пример — модуль sort, который написан авторами гоу и который должен сортировать массивы. Он позволяет отсортировать только массивы из нескольких встроенных массивов — тех, что авторы сами лично определили в библиотеке:


type IntSlice []int
type Float64Slice []float64
type StringSlice []string

То есть да, у вас есть три типа, которые вам можно использовать, а остальные — нет.


Более того, код функции Swap в этих трёх типах был просто продублирован три раза. Да, я совершенно серъёзно, они закопипастили один и тот же код три раза. Более того, чтобы расширить возможности этой библиотеки ещё на другие типы — этот код снова надо копипастить для КАЖДОГО типа, который вы хотите поддержать.


func (x     IntSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x Float64Slice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x  StringSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }

В СиШарпе, к примеру, ты можешь сортировать массив из любых элементов, которые реализуют IComparable, ну или передать отдельный IComparer<T> — сравниватель двех элементов.


И, представьте, ничего не копипаститься для каждого нового типа.

В СиШарпе, к примеру, ты можешь сортировать массив из любых элементов, которые реализуют IComparable

который точно так же скопипащен в каждом типе:


        public int CompareTo(int value) {
            // Need to use compare because subtraction will wrap
            // to positive for very large neg numbers, etc.
            if (m_value < value) return -1;
            if (m_value > value) return 1;
            return 0;
        }

        public int CompareTo(Int64 value) {
            // Need to use compare because subtraction will wrap
            // to positive for very large neg numbers, etc.
            if (m_value < value) return -1;
            if (m_value > value) return 1;
            return 0;
        }

        public int CompareTo(String strB) {
            if (strB==null) {
                return 1;
            }

            return CultureInfo.CurrentCulture.CompareInfo.Compare(this, strB, 0);
        }

Но вы не понимаете, это другое?

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


А вот свап двух элементов массива писать — глупо. Ведь как два элемента массива между собой поменять — совершенно не зависит от наполнения массива. Это глупый, ненужный копипаст. Разницу улавливаете?


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

Сравнение типа писать — нормально

Тогда, если вы приводите контрпример в виде свопа двух элементов — то пример свопа и надо приводить.
Но мне почему-то кажется, что, к примеру, для свопа элементов в linked list и array код свопа тоже разный будет (но здесь возможно и нет, думаю если есть возможность определить оператор скобок, то и код свопа одинаков будет). Пакет sort не делает предполжений о том, что ему передают именно массив.


Пакет sort позволяет отсортировать вам слайсы любого типа, которые реализуют интерфейс sort.Interface ровно точно так же, как C# позволяет отсортировать коллекции объектов, типы которых реализуют IComparable.


сам по себе сравниватель должен лежать в клиентском коде, возле самих типов

Он там и будет лежать, потому что для клиентских типов слайсов вы его и будете определять в клиентском коде. Разница в данном случае лишь в том, что в C# вам надо для типа элемента массива определить CompareTo, а в Go — для всего массива метод Less.
А для стандартной библиотеки языка "клиентским кодом" будет являться сама стандартная библиотека, поэтому в ней и лежит сравниватель для типов, определённых непосредственно в самом языке.

Но мне почему-то кажется, что, к примеру, для свопа элементов в linked list и array код свопа тоже разный будет

И это тоже нормально. Вот только на примере СиШарпа для Гоу нужно было бы переопределять своп не LinkedList и ArrayList.


А List<Cat>, List<Dog>, List<Animal> и всех остальных листов. Для каждого возможного Т в листе, который мы хотим сортировать нужно определить три метода.


А если мы хотим сортировать ArrayList<Cat> и LinkedList<Cat>, то для каждого из них — тоже заново написать функцию сравнения котов. Понимаете в чём разница?

Понимаете в чём разница?

В чём разница — я понимаю, но в СиШарпе вам так же нужно написать какие-то примитивы, чтобы пресловутая сортировка в контейнерах работала. Да, их будет меньше. Да, их не нужно писать для каждого контейнера. Но есть нюанс: во время работы вам нужно сортировать не "каждый возможный T", а вполне конкретный контейнер с объектами конкретного типа. Не во всяком проекте нужно сортировать много различных контейнеров, у которых различное содержимое.


Не во всяком проекте вообще нужно сортировать хоть что-нибудь. Зачастую при работе с данными из БД они уже приходят сортированными и от используемого языка это зависит примерно никак.
В таком случае, если вы не будете использовать сортировку, какое преимущество вы получаете от дженериков, если в любом языке вам не нужно будет писать дополнительный код для этого?


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

И, представьте, ничего не копипаститься для каждого нового типа.

В mscorlib код Console.WriteLine выглядит вот так:


        [HostProtection(UI=true)]
        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        public static void WriteLine(float value)
        {
            Out.WriteLine(value);
        }   

        [HostProtection(UI=true)]
        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        public static void WriteLine(int value)
        {
            Out.WriteLine(value);
        }

        [HostProtection(UI=true)]
        [CLSCompliant(false)]
        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        public static void WriteLine(uint value)
        {
            Out.WriteLine(value);
        }

И так 15 раз. Да, я совершенно серьёзно, они закопипастили один и тот же код 15 раз. В последнем случае декораторы разные, но в остальном код почти для всех типов один и тот же.
Кроме того, чтобы WriteLine какую-то полезную информацию выводил для своих классов, придётся переопределить ToString, скорее всего. Но я сварщик не настоящий.

Ещё раз. Я не говорю, что весь язык динамический, но конкретно у этой механики — динамическая природа.
Ещё раз: вы путаете динамику и полиморфизм. Присваивание переменной типа interface{} значения, реализующего interface{} — это именно полиморфизм, а не динамика.
Я напомню, что Питон не приводит никакие типы автоматически, но в нём всё ещё динамическая типизация.
У вас странные представления о Python: 25 + True (абсолютно корректное Pytnon-выражение) — типичное для языков со слабой типизацией автоматическое преобразование типов.

Но, похоже, вы не поняли: я говорил не про автоматическое преобразование значения в значение другого типа (это и есть слабая типизация), а про возможность языка автоматически воспользоваться информацией о реальном типе значения, присвоенного переменной-интерфейсу (interface{} — лишь частный случай такого присваивания). Именно наличие этой возможности отличает динамическую типизацию от статической. И именно потому в Go динамической типизации нет.
Ещё раз: вы путаете динамику и полиморфизм

Ничего я не путаю. Способ указать "тут может быть что угодно" ещё не называется полиморфизмом, как и то, что я могу спрыгнуть с горы ещё не означает, что я умею летать, хотя в воздухе какое-то время я, конечно пробуду. То, что оно в гоу называется красивым словом "interface" ещё не означает, что это сразу стало полиморфизмом, а то, что в TypeScript называется словом any — не стало. Более того, сам по себе полиформизм ещё не означает отсутствие динамики. Полиформизм есть точно так же в полностью динамических языках.


Суть полиформизма в том, что вам не нужно уточнять тип, чтобы с ним работать. Ну, к примеру:


interface IFoo {
    void DoSmth();
}

void Method (IFoo foo) {
    foo.DoSmth();
}

class Bar : IFoo {};
class Qux : IFoo {};

Method(new Bar());
Method(new Qux());

Вы видите, что я передаю в метод разные типы, но продолжаю с ними работать как с одним. Это — полиморфизм. А вот использовать мета-тип "any" и потом кастить его к нужному типу и получать исключение — это динамика, а не полиморфизм. Ведь проверка и уточнение типа происходит в рантайме, а не в компайл-тайме.

к примеру

Но ведь интерфейсы в Go делают тоже самое в компайл-тайме...


type DoSmther interface {
    DoSmth()
}

func Method(DoSmther foo) {
    foo.DoSmth()
}

type Bar struct { }
func (*Bar) DoSmth() { }

type Qux struct { }
func (*Qux) DoSmth() {}

Method(&Bar{})
Method(&Qux{})

И конкретно это — абсолютно нормальный код. И конкретно это — полиморфизм. Вот только вы говорите сейчас про совершенно другую механику.
Мы обсуждаем необходимость использования пустого интерфейса в Гоу. В обычном коде на других языках вы врядли встретите постоянное использование object или any, а в Гоу — это в порядке вещей.

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


В Go использование пустых интерфейсов — не в порядке вещей. Я специально прямо сейчас посмотрел, где в проекте, с которым я работаю, используется пустой интерфейс. Таких пакетов всего три: обёртка над protobuf для сериализации и отправки пакетов, пакет для сериализации объектов и записи их в etcd и пулер для БД, который просто аргументы передаёт в pgx. Во всех этих местах при этом явного ручного приведения типов нет нигде. Да, они внутри encoding/json и библиотеке для protobuf есть, но это входит в класс задач, для которых, как я не раз упоминал, без интерфейсов не обойтись. Но это не "в порядке вещей", а понятные задачи.

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

Этот код я привожу как объяснение того, что такое полиморфизм. А пустой интерфейс — это аналог типа object. Вы читайте, пожалуйста, внимательнее.

Да, они внутри encoding/json и библиотеке для protobuf есть, но это входит в класс задач, для которых, как я не раз упоминал, без интерфейсов не обойтись.

В силу недостаточной выразительности языка.

Так никто и не спорит же. Да, есть такие задачи где дженерики сделали бы работу более удобной и безопасной, чем сейчас. Вот конкретно эти вещи могли бы в принципе и получить преимущества (правда в том виде, в котором они предложены в proposal я не очень понимаю как encoding/json их может использовать). Но никакой всеобъемлющей проблемы отсутствие дженериков в Go не несёт и пустыми интерфейсами код обычных приложений не изобилует, вопреки распространённому мнению.

Ещё как нужны. Есть слайсы, мапы и каналы, и они как раз таки обобщённые.

Надо же так переврать официальный блог!

  1. Proposal только стартовали, а никак не принят
  2. Будет только в «future version of Go»

В черновике 2019 года для задания ограничений вводилась новая сущность «contract»: go.googlesource.com/proposal/+/master/design/go2draft-contracts.md

В актуальном варианте от контрактов отказались в пользу интерфейсов:
go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md

Наконец Го становится более-менее юзабельным?

Дженерики нужны далеко не всем. Так что, для многих, он уже давно юзабельный)

Он нужен для наверное почти всех библиотек которые сейчас подключены у меня в проекте. Ничего серхъестественного: логгеры, ормки, диай,…
Библиотеки тоже нужны не всем?

Библиотеки тоже нужны не всем?
Я об этом не говорил. И даже не делал такого вывода, разумеется.

Я имею ввиду, что не все пишут библиотеки общего назначения. Кто-то пользуется библиотеками и не очень переживает, есть внутри дженерики или нет. И вполне себе пишут много разного бизнес-код и прочего.


Я не против дженериков. Я против вывода, что без них язык "недоязык"

Я имею ввиду, что не все пишут библиотеки общего назначения. Кто-то пользуется библиотеками и не очень переживает, есть внутри дженерики или нет.

Но пользуются-то все. И вот как раз для пользователя есть огромная разница в удобстве работы. Можно просто посмотреть на сишарп версии 1.1 и 2.0, тоже без и с генериками.


Я не против дженериков. Я против вывода, что без них язык "недоязык"

Ну, я таких утверждений конечно не делаю, но мне определенно трудно представить продуктивную разработку без них. Пробовал — не получилось. Если в динамике ещё можно как-то накостылить манкипатчингом объектов в рантайме и прочей грязью, то тут и этого не было

А у меня вроде бы получается. Да, есть места, где их не хватает, бесспорно. Но в целом жить можно. Повторюсь, все это мы обсуждаем в контексте о "более-менее юзабельность")

Да, я считаю его неюзабельным. Вы правда хотите пробовать меня переубедить в этом?

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

Interface types used as type constraints can have a list of predeclared types; only type arguments that match one of those types satisfy the constraint.

То есть всё-таки решили накостылить. Не будет унификации между встроенными и пользовательскими типами.

Перешел с Php на Go. При всех его преимуществах и особенностях, к которым можно привыкнуть, []type.interface != []interface и отсутствие дженериков это реальная боль. С дженериками можно будет уже работать с более привычными структурами.
в PHP тоже нет дженериков )
Так они там и не нужны. ЯП с динамической типизацией.
НЛО прилетело и опубликовало эту надпись здесь
После введения в PHP 7 нормального контроля типов пошли запросы на добавление в язык обобщённого программирования. А в PHP 8 возможности типизации ещё расширили… Так что и в PHP ждём.

P.S. Существующие в языке типажи (trait) очень плохо сочетаются с типизацией.
Полагаете, что те парни, которые кодили и кодят Syncthing (к примеру, весьма крупный Go- проект) испытывали и испытывают реальную боль от отсутствия generic?
Осталось дождаться возможности циклической загрузки пакетов без проблем :(
Можно начинать изучать язык.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Другие новости

Истории