Comments 88
Интересно, что о приведении типов сказано примерно ничего.
Как можно при компиляции проверить соотношение между параметрами? А если значения приходят из сети?

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

Компилятор в языках с зависимыми типами проверяет не сами соотношения между параметрами, а специальные объекты-утверждения. Скажем, тип «less<a,b>» описывает утверждение «a меньше b». Тогда какая-нибудь функция, возвращающая список чисел между a и b, может иметь вид
    list<int> range(int a, int b, less<a,b> property);

Чтобы вызвать эту функцию, потребуется не только подать ей два числа, но и некое правило, по которому для данных двух чисел можно построить объект less<a,b>. Конечно, в таких языках нет null, bottom, небезопасных приведений типов и прочих «игр» с системой типов, которые бы могли ее скомпрометировать.

Такие ЯП делают большое различие между операцией «проверить, что a меньше b» и свойством «a меньше b». Первое — простая функция
bool is_less(int a, int b);

Второе — объект особого типа
template<int a, int b>
struct less;
который нужен только компилятору для проверкт корректности, но никогда реально не используется в рантайме.

Для простых случаев (например, двух констант) компилятор часто может вывести построение свойства самостоятельно. Для «спорных» его придется строить программисту, с поддержкой компилятора.
А для ситуаций, где свойство необязательно выполнено (числа приходят по сети), придется делать ветвление и обрабатывать некорректные входные данные. Это и есть самая крутая фича языков с зависимыми типами — программа или работает в соответствии со спецификацией, или не компилируется, что позволяет сконцентрировать все усилия на составлении и проверке спецификации.
Дополню себя и комментарий VoidEx примером того, как можно пользоваться условиями, чтобы получить свойство «a < b».

Операции сравнения обычно немного более сложны, чем в обычных языках и возвращают не просто bool, а одно из двух свойств: либо «a < b», либо «a < b есть ложь». Продолжая аналогию с C-подобным синтаксисом:

    //Последовательный вариант с возвратом boost::variant
    boost::variant< less<a,b>, not< less<a,b> > >  compare_less(int a, int b);

    //Вариант с callback
    T compare_less(int a, int b, function<T(less<a,b>)>, function<T(not< less<a,b> >)>);


Подавая на вход два числа, мы получим, тем или иным способом, объект свойства, который можно использовать для передачи в нашу функцию range:

    //Вариант с boost::variant
    list<int> listFromNetwork(){
        int a = readFromNetwork()
        int b = readFromNetwork()
        auto compare_result = compare_less(a, b);
        if ( less<a, b>* property = boost::get< less<a, b> >(compare_result) ){
            return range(a, b, *property);
        }else{
            //Обработка ошибки
        }
    }

    //Вариант с callback
    list<int> listFromNetwork(){
        int a = readFromNetwork()
        int b = readFromNetwork()
        return compare_less(a, b, [&](less<a,b> property){
               return range(a, b, property);
           }, [&](not< less<a,b> >){
               //Обработка ошибки
           });
    }
Доказав это на этапе компиляции тем или иным способом.
Например, для положительного числа x, пришедшего по сети, число x*2 + 1 всё равно больше, чем x, и такую пару можно передать без проверок.
Если же про оба числа ничего неизвестно, то, понятно, надо сделать динамическую проверку руками до вызова функции. Например, так:

if x < y then ... тут x < y, можно звать функцию ... else ... а тут нет ...


см Изоморфизм Карри — Ховарда
И при этом компилятор уже видит, что в ветке «можно звать функцию» x и y удовлетворяют типу функции?
Более подробен комментарий выше моего. Суть в том, что благодаря упомянутому изоморфизму, утверждение «x > y» — это такой тип. Если у него есть хотя бы одно значение, значит утверждение верно. Например, у типа «1 > 0» есть значение. Вот код на Агда:

-- натуральное, например Succ (Succ Zero) - это 2
data Nat : Set where
	Zero : Nat -- ноль
	Succ : Nat → Nat -- n + 1

-- тип, утверждение, что одно число не меньше другого
data _≥_ : Nat → Nat → Set where
	-- любое число больше нуля
	≥Zero : {n : Nat} → n ≥ Zero
	-- если n ≥ m, то n + 1 ≥ m + 1
	Succ≥Succ : {n m : Nat} → n ≥ m → Succ n ≥ Succ m

proof₁ : Succ Zero ≥ Zero -- 1 ≥ 0
proof₁ = ≥Zero -- доказали

proof₂ : Succ (Succ Zero) ≥ Succ Zero -- 2 ≥ 1
proof₂ = Succ≥Succ proof₁ -- тоже


Таким образом, упомянутая функция помимо двух чисел принимает доказательство того, что одно из них больше другого. Доказательство можно либо вывести из чего-либо, либо получить «нахаляву» внутри одной из веток if. Т.е. внутри then у нас есть значение типа x < y (в примере на Idris: LT x y), ну а внутри else есть доказательство отрицания этого утверждения.

Т.е. строго говоря не «в ветке x и y удовлетворяют типу функции», а «в ветке есть значение-доказательство требуемого утверждения — что x < y».
>>> число x*2 + 1 всё равно больше, чем x

ваша неправда… только при х > -1
Вроде все положительные числа этому условию удовлетворяют?
UFO landed and left these words here
Возможно и есть — мир огромен.;)

freetonik перевел слово «collection» как «коллекция» а в данном случае нужно перевести как «совокупность».
Это две большие разницы.
В целом, тип данных это множество в математическом смысле. Так что считаю «коллекцию» нормальным переводом.
UFO landed and left these words here

Мне кажется, вот в языках типа Idris, Agda и Coq так оно и есть. Там типы можно определять перечисляя все возможные значения, либо индуктивно. В любом учебнике вот так определяют натуральные числа:


Z : Nat // ноль — натуральное число
succ Nat : Nat // любое число, следующее за натуральным — натуральное

И получается, что множество натуральных чисел — это бесконечное множество {Z, succ Z, succ (succ Z), ...}.

UFO landed and left these words here

Ну, функции на типах — это самая суть зависимой типизации и есть. Согласуется ли это с понятием коллекции — вопрос дискуссионный. Мой поинт в том, что современные языки с зависимыми типами оперируют вещами на уровне, достаточно близком к приведённым в статье (полу-) формальным определениям.

А в каких языках это не так?

Не единственное, но вполне подходящее определение для типа, по-моему.
UFO landed and left these words here

С точки зрения теории типов ваш T равномощен множеству всех возможных функций void () (то есть функций, не принимающих и не возвращающих никаких объектов).

UFO landed and left these words here
Отчего ж не говорит. Вы можете построить изоморфизм между целыми и натуральными (0, 1, 2… <-> 0, 2, 4...; -1, -2 <-> 1, 3...). Я так понимаю, в HoTT ещё и применить аксиому унивалентности (A ≃ B) ≃ (A = B).
UFO landed and left these words here
Да ладно Вам, тут объясняют, откуда дети берутся, а Вы к терминам придираетесь…
Еще можно добавить про значительный минус динамической типизации, например, когда делаю портирование библиотеки с питона в шарп, приходится постоянно проходить для каждого метода всю цепочку вызовов, и все равно может остаться неясным с какими же типами работает автор. Читабельность хуже в разы чем у языков со статической типизацией. Собственно из-за таких проблем и не могу представить использование питона в бизнес-решениях, только в качестве небольших скриптов утилитарного назначения. (конечно проблема пропадет если автор будет оставлять подробные комментарии и указывать вход, выход и описание)
В случаи c TypeScript можно напомнить про SoundScript, попытку инженеров гугла внести больше смысла в такую типизацию.
Я сторонник статической типизации.
Динамическая типизация всегда может быть частью статической. Пример — упоминавшийся уже тип any, который как раз и представляет собой универсальный динамический тип. Если в статически типизированном языке с any использовать только any, то скорее всего получится динамически типизированная программа.
Можно сделать и промежуточные универсальные типы — например тип number для любых чисел — целых, с плавающей точкой и любой величины.
В тех же случаях когда программисту точно известно, что ему нужен int32 и ни что другое, зачем лишать его такой возможности?
Динамическая типизация всегда может быть частью статической.

… если под это написать отдельный рантайм, ага.


Пример — упоминавшийся уже тип any, который как раз и представляет собой универсальный динамический тип.

Тип any — это в тайп-скрипте, который всего лишь типизированный препроцессор к динамическому языку?

Тип any — это в тайп-скрипте, который всего лишь типизированный препроцессор к динамическому языку?

Да, но обобщить несложно, не так ли?
Тип Dynamic и в Хаскеле есть, к примеру. Другое дело, что там им неудобно пользоваться, но это уже ортогональный вопрос.
Вот посмотрите также на Soft types для ML-like языка.

Совсем вкратце про soft types: добавляются дополнительные правила вывода. В некоторых конкретных случаях при ошибке вывода выводится dynamic/any. Typescript, собственно, вроде как раз soft types и есть, просто он совместим с JavaScript, что накладывает ограничения. В статье приводится ML-like статический язык с такими же особенностями.
Да, но обобщить несложно, не так ли?

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

Вы статью почитали? Язык, приведённый там — он со статической или динамической типизацией?
Если «построить отдельный рантайм» с нужными свойствами, получится язык со статической или динамической типизацией?

Судя по фразе "explicit run-time checks in programs for which the type assignment algorithm fails" — как минимум смешанный. Но вообще выглядит как динамический.


Но вполне вероятно, что я еще не все понял.

Введение Dynamic в Хаскель не сделало его динамическим:

let x = dynApp (toDyn (*20)) (toDyn 10)
let y = dynApp (toDyn negate) x
let z = dynApp (toDyn (show :: Integer -> String)) y -- стандартный show не умеет в динамику
let x' = fromDynamic x :: Maybe Integer -- Just 200
let y' = fromDynamic y :: Maybe Integer -- Just -200
let z' = fromDynamic z :: Maybe String -- Just "-200"
let k = dynTypeRep z == typeRep (Proxy :: Proxy String) -- True, тип String


Другое дело, что чтобы этим было удобно пользоваться, надо написать дополнительные модули, но технически это можно сделать. Конкретно Хаскельный Dynamic для этого плохо приспособлен (его задачи проще — конвертнуться туда-обратно, а не работать напрямую с ним), так как там только метка типа и всё, а было бы хорошо туда положить ещё служебных функций (show, op_plus, op_equal...).
Собственно, если прикрутить soft types, при котором стандартный вывод типов иногда выводит Dynamic, это (наличие удобной работы) становится необходимым. Система типов же от этого не становится динамической, все типы известны статически, иногда это any/dynamic. При динамике же у всех переменных статически один тип — any/dynamic.

В языке по ссылке при отсутствии определённых конструкций типа
if cond then 1 else "blah"
все типы выведутся и всё будет статическим. Какой же он динамический-то?
Посмотрите, какой тип он выводит так такой штуки — функции, проверяющей, что поданная на вход функция (с любым кол-вом аргументов) всегда вернёт true на любом входе.

taut = λB . case B of
	true : true
	false : false
	fn : ((and (taut (B true))) (taut (B false)))

taut : β → (true + false) where
	β = fix t . (true + false + ((true + false) → t)) -- рекурсивный тип


Если упростить тип для понимания, то он такой:
taut : β → bool where
	β = bool | bool → β -- либо bool, либо bool → bool, либо bool → bool → bool, либо...


Кстати, немного оффтоп, есть такой канонический пример нетипизируемого лямбда-выражения:
λx. (x x)

В Agda, например, его можно типизировать:

S : {a : Set} {β : Set → Set} → ({α : Set} → α → β α) → β (a → β a)
S x = x x

id : {a : Set} → a → a
id x = x

test : {a : Set} → a → a
test = S id


Собственно, если прикрутить soft types, при котором стандартный вывод типов иногда выводит Dynamic, это (наличие удобной работы) становится необходимым

Вот это "наличие удобной работы" и есть тот "отдельный рантайм", про который я говорю.

Вот это «наличие удобной работы» и есть тот «отдельный рантайм», про который я говорю.

Если сделать в Хаскеле нормальный Dynamic, который помимо метки типа будет таскать словарь со служебными функциями, то рантайм писать не надо, надо написать модули для работы с этим в рамках текущего рантайма. Возможно, я что-то не учёл?
Служебные функции типа to_str, op_equal и т.п., чтобы реализовав их для своего кастомного типа, его можно было бы сразу класть в Dynamic и орудовать им. Ибо сейчас в Dynamic можно положить любой Typeable, но с ним можно не глядя сделать только одно — попробовать конвертнуть в заданный тип. Если потребовать класть словарь с кучей функций, то можно будет складывать, преобразовывать в строку, сравнивать и т.п.

Могу даже попробовать на досуге реализовать такой Dynamic, если есть интерес, вдруг я что-то не учёл.

На систему типов это не влияет.

Вот эти "модули для работы" и будут вашим рантаймом. Дальше вопрос того, насколько именно будет удобно программисту.

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

Ну так я обратного и не утверждал.

Явно — нет.
Но вообще выглядит как динамический.

Собственно, дальше я просто показывал, что dynamic и наличие удобной с ним работы — это не [обязательно] динамическая типизация.

Да и в исходном комментарии вот это прозвучало якобы как возражение:
Тип any — это в тайп-скрипте, который всего лишь типизированный препроцессор к динамическому языку?

В Скале, например, есть Any, хотя детально его возможности я не знаю. Можно ли там орудовать одним Any или нет. Ну и про Dynamic в Хаскеле я уже упоминал.
Да и в исходном комментарии вот это прозвучало якобы как возражение:

Это относилось только к конкретному any в конкретном TypeScript. А выше в исходном комментарии было сказано, что чтобы встроить динамическое поведение в статический язык, надо "под это написать отдельный рантайм".

Я распарсил это как «any из TypeScript? Плохой пример динамики внутри статики, так как это всего лишь типизированный препроцессор, а не нормальная статика». Извините, если прочёл не так.

Вы прочли совершенно правильно. Это именно "всего лишь типизованный препроцессор", в рантайме язык себя ведет как динамический во всех местах.

В Скале, например, есть Any, хотя детально его возможности я не знаю. Можно ли там орудовать одним Any или нет.

Смотря что вы имеете ввиду. Ничего разумного кроме стандартных операций класса j.l.Object (для примитивов Any в этом случае будет boxed-значением), isInstanceOf[T] и asInstanceOf[T] не сделаешь.


Нет не то что привычных для динамики аналогов method_missing, даже простого динамического вызова не сделаешь (если не писать рантайм своего динамического языка, конечно).


val m = Map("a" -> 1)
val a: Any = m

m.get("a") // would be Some(1)
a.get("a") // compilation error

Динамически типизированный язык не должен запрещать выражение вида a.get(...), т. к. в переменной a может лежать и что-то поддерживающее метод get, как в данном случае.


Так что scala — нормальный статически типизированный язык.

Не нужно быть сторонником.
У этих подходов просто разные сферы применения.

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

При этом сам язык не обязательно должен обладать статической типизацией: на примере PHP можно увидеть, что практически во всех популярных библиотеках типы определены через комментарии (phpdoc), и инструменты используют их для статического анализа.
Про типизацию не очень интересно. Я уже давно сделал свой жизненный выбор в пользу статической.
Но зато я обогатил свой лексикон новым выражением — «почти гарантированно».
Статически типизированные языки ограничивают типы переменных: язык программирования может знать, например, что x — это Integer. В этом случае программисту запрещается делать x = true, это будет некорректный код. Компилятор откажется компилировать его, так что мы не сможем даже запустить такой код.
Но ведь для статических языков со слабой типизацией это может быть неверно. Всё зависит от наличия приведения типов.

Однако, в большинстве статически типизированных языков выражение «a» + 1 — это не программа: она не будет скомпилирована и не будет запущена. Это некорректный код, так же, как набор случайных символов !&%^@*&%^@* — это некорректный код.
Это слишком радикальное утверждение. Обычно процесс компиляции состоит из нескольких этапов. Если код проходит этапы лексического и синтаксического анализа и позволяет компилятору провести проверку типов, то он намного ближе к корректной программе, чем набор случайных символов. Конечно, с точки зрения пользователя это несущественная разница (и то, и то не компилируется), но с точки зрения компилятора разница есть.

Ну и опять же, компилируемость «a» + 1 зависит не от того, статическая или динамическая типизация в языке, а от того, есть ли в нем неявное приведение типов для конкретного случая и реализован ли в нем оператор сложения для нужных типов. Например, в Ruby «a» * 3 выдает «aaa». В C++ тоже можно сделать свой строковый тип и реализовать такое же поведение. И что теперь, C++ тоже динамически типизирован? Нет.
Но ведь для статических языков со слабой типизацией это может быть неверно. Всё зависит от наличия приведения типов.

Неважно, есть или нет приведение типов. Если тип переменной x — целое число, то туда и попадает в итоге целое число, даже если оно до приведения было строкой.

Это слишком радикальное утверждение.

Пример с «a» + 1, может быть, не очень наглядный, но вообще-то автор прав:
Цель статической проверки типов — не допустить потенциально некорректную (с точки зрения типов) программу к выполнению (при этом могут быть отклонены и некоторые корректные программы).
Цель динамической проверки — не зарубить корректную программу (но во время выполнения могут возникать ошибки типов).
В этом принципиальная разница.

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

А посмотрите, интересно получается. Цитата из статьи:


Группа три находится на пороге входа в мэйнстрим, с большой поддержкой со стороны Mozilla (Rust) и Apple (Swift).

Получается, сюда надо добавить ещё Microsoft и Facebook (вроде бы, они очень активно применяют у себя OCaml — во всех этих новомодных Flow и Reason).


То есть нас почти вся индустрия затягивает — используйте функциональные языки, используйте. А мы (не все, но как минимум некоторые) старательно отнекиваемся ,)

UFO landed and left these words here

Особенно понравилось про GO...


Невозможно задать тип MyList, который бы означал «список целых чисел», «список строк» и т.д..

type MyList []string

Это срез (эдакий fat pointer с собственно указателем, limit и capacity) с точки зрения системы типов. Но ниже лежит фиксированный массив. Если хочется именно массив то надо вообще указывать конкретный размер в типе: [42]string.


А листов и прочих более высокоуровневых коллекций у них просто нет (кроме map[t1]t2).

Если уж так нужен list.В стандартной поставке есть "container/list", реализовав интерфейс которого, можно получить нужное поведение, или если лень, использовать сам пакет. Хотя если честно никогда этим не пользовался.

interface{} он возвращает, так и скажите. На котором все достоинства статической типизации и заканчиваются.

И вот хороший пример того, к чему это приводит:


    var x list.List
    var u string = "aaa"
    var u2 int = 2
    x.PushBack(u)
    x.PushBack(u2)

    for e := x.Front(); e != nil; e = e.Next() {
        fmt.Println(reflect.TypeOf(e.Value))
    }

string
int

Конечно он вернет интерфейс. Это вполне логично. Чтобы этого не было нужно реализовать интерфейс лист для своего типа. Но мы как то зациклились на list. Это некая абстракция. Какое поведение должно быть у list и чем конструкция []T не вписывается в это поведение.

Конечно он вернет интерфейс. Это вполне логично

Это логично в рамках Go, но неудобно для разработчика.


Чтобы этого не было нужно реализовать интерфейс лист для своего типа.

Это в Go. В языках с дженериками этого не нужно.


Какое поведение должно быть у list и чем конструкция []T не вписывается в это поведение.

Добавление элементов за O(1), например?


Но вообще просто представьте на месте списка любую другую коллекцию: set, stack, queue, priority queue, union/find и так далее. Проблема-то та же самая.

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

Это касается только уже известных компилятору типов?(int, string, etc) к примеру List list = new List не требует реализацию интерфейса для сортировки?

Насчет o(1);
o = append(o, 1)
Насчет удобства, возможно вы правы, чем меньше кода тем лучше.

Вопрос не только количества кода, но и его статической верифицируемости (а именно здесь между статической и динамической типизацией большое отличие).


Это касается только уже известных компилятору типов?(int, string, etc)

Все типы известны компилятору. Если вы имели в виду "только примитивных типов" или "только типов из стандартной библиотеки", то нет, любых вообще, существующих в программе.


не требует реализацию интерфейса для сортировки?

А при чем тут сортировка вообще?

то нет, любых вообще, существующих в программе.

Ок. Тогда вернемся к поведению которое требуется. В Go slice или array может выступать в роли списка. И хранить в себе определенный тип T. Теперь я хочу уловить, чем это принципиально отличается от конструкции типа:
List<someClass> list = new List<someClass>
?
А при чем тут сортировка вообще?


Разве в той же java
List<int> 

не умеет .sort()?
Теперь я хочу уловить, чем это принципиально отличается от конструкции типа:

Тем, что слайс — не список. Ну и да, давайте просто заменим список на стек, чтобы проблема была нагляднее.


Разве в той же java List<int> не умеет .sort()?

Про Java ничего не знаю, врать не буду. В .net List<T> умеет сортировку. Дефолтное поведение ищет дефолтный же компаратор (и для него нужно, чтобы T: IComparable<T> или T: IComparable), недефолтное просто принимает компаратор в себя.

Java и C# в этом отношении языки с не самой мощной системой типов.

В тех же scala и haskell подобный метод требует доказательства того, что элементы коллекции можно сравнивать:
scala> Seq("a", "c", "b").sorted
res0: Seq[String] = List(a, b, c)

scala> case class A(s: String)
defined class A

scala> Seq(A("a"), A("c"), A("b")).sorted
<console>:13: error: No implicit Ordering defined for A.
       Seq(A("a"), A("c"), A("b")).sorted
                                   ^

scala> implicit val aIsComparable = implicitly[math.Ordering[String]].on[A](_.s)

scala> Seq(A("a"), A("c"), A("b")).sorted
res2: Seq[A] = List(A(a), A(b), A(c))

Насчет o(1): o = append(o, 1)

И получили потенциально новый объект. Вот именно этим от привычного List и отличается.

И получили потенциально новый объект. Вот именно этим от привычного List и отличается.

т.е
List<int> list
list.push(1)

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

Нет.


мне кажется это остается под катом. а внутри происходит то же самое.

Здравствуй, инкапсуляция. Не важно, что внутри, важен внешний интерфейс.

Здравствуй, инкапсуляция. Не важно, что внутри, важен внешний интерфейс.


А как же вот это для Java

Что "вот это"?


(ну и вообще, Java в этом контексте меня мало интересует, у Java type-erasure generics)

Если конкретнее то вот это.

    private void More ...grow(int minCapacity) {
         // overflow-conscious code
          int oldCapacity = elementData.length;
          int newCapacity = oldCapacity + (oldCapacity >> 1);
          if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
          if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
          // minCapacity is usually close to size, so this is a win:
          elementData = Arrays.copyOf(elementData, newCapacity);
     }


это к вопросу о том что под катом.

Ну да, типичная реализация динамически-приращиваемого списка. И что?

Но вообще как бы разговор не о том, а о том чего же Вам не хватает в конструкции type T1 []T2.

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

Аргумент. Согласен, возможно это было бы удобно.

Для абсолютного любого можно использовать reflect, осторожно конечно. Но это костыль наверное.Для конечного кол-ва типов Вы можете это сделать реализуя интерфейс, но как я уже говорил — это требует большего количества кода. Т.к каждый тип должен реализовывать этот интерфейс.

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

Знаете, как человек, который каждый день пишет на языке, где это норма — это не "возможно удобно", это очень удобно.


Система типов в go отвратительно готовит динамические данные

В данном случае речь идет о сугубо статическом коде.

В данном случае речь идет о сугубо статическом коде.

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

Лично я пока что не сталкивался с задачами которые бы принципиально требовали реализации таких механизмов. Хотя наверняка они есть.
Если рассматривать вопрос может он это или нет, то может.

Не может. Go (в текущей версии) принципиально не может дженерики. Это характеристика его системы типов, про которую и написано в посте.


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

Возможно я не понял о чем речь, но
Невозможно задать тип MyList, который бы означал «список целых чисел», «список строк» и т.д.

В Go можно задавать тип «список целых чисел» и с ним работает как со списком целых чисел.
https://play.golang.org/p/22-H1HRyyA

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

Под списком подразумевается какое то особое особое поведение?

Да.


Если таки нужно поведение, вы реализуете нужные интерфейсы и его получаете.

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


Тема о том почему типы в GO это шаг назад в любом случае не раскрыта.

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

Ликбез по типизации:
https://habrahabr.ru/post/161205/

>например, что x — это Integer. В этом случае программисту запрещается делать x = true, это будет некорректный код.

Может это все же сильная типизация?

>«a» + 1 (язык Си — это исключение из этого правила)

Потому что язык С — язык со слабой статической типизацией…

>Многие статически типизированные языки требуют обозначать тип.

Это называется явная типизация.

>Большинство динамических языков выдадут ошибку, если типы используются некорректно

Это о слабая — сильная типизация…
Only those users with full accounts are able to leave comments. Log in, please.