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



Тип — это коллекция возможных значений. Целое число может обладать значениями 0, 1, 2, 3 и так далее. Булево может быть истиной или ложью. Можно придумать свой тип, например, тип "ДайПять", в котором возможны значения "дай" и "5", и больше ничего. Это не строка и не число, это новый, отдельный тип.


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


Динамически типизированные языки помечают значения типами: язык знает, что 1 это integer, 2 это integer, но он не может знать, что переменная x всегда содержит integer.


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


Статически типизированные языки


Статические языки проверяют типы в программе во время компиляции, еще до запуска программы. Любая программа, в которой типы нарушают правила языка, считается некорректной. Например, большинство статических языков отклонит выражение "a" + 1 (язык Си — это исключение из этого правила). Компилятор знает, что "a" — это строка, а 1 — это целое число, и что + работает только когда левая и правая часть относятся к одному типу. Так что ему не нужно запускать программу чтобы понять, что существует проблема. Каждое выражение в статически типизированном языке относится к определенному типу, который можно определить без запуска кода.


Многие статически типизированные языки требуют обозначать тип. Функция в Java public int add(int x, int y) принимает два целых числа и возвращает третье целое число. Другие статически типизированные языки могут определить тип автоматически. Та же самая функция сложения в Haskell выглядит так: add x y = x + y. Мы не сообщаем языку типы, но он может определить их сам, потому что знает, что + работает только на числах, так что x и y должны быть числами, значит функция add принимает два числа как аргументы.


Это не уменьшает "статичность" системы типов. Система типов в Haskell знаменита своей статичностью, строгостью и мощностью, и в по всем этим фронтам Haskell опережает Java.


Динамически типизированные языки


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


def f(x, y):
    return x + y

может складывать два целых числа, склеивать строки, списки и так далее, и мы не можем понять, что именно происходит, пока не запустим программу. Возможно, в какой-то момент функцию f вызовут с двумя строками, и с двумя числами в другой момент. В таком случае x и y будут содержать значения разных типов в разное время. Поэтому говорят, что значения в динамических языках обладают типом, но переменные и функции — нет. Значение 1 это определенно integer, но x и y могут быть чем угодно.


Сравнение


Большинство динамических языков выдадут ошибку, если типы используются некорректно (JavaScript — известное исключение; он пытается вернуть значение для любого выражения, даже когда оно не имеет смысла). При использовании динамически типизированных языков даже простая ошибка вида "a" + 1 может возникнуть в боевом окружении. Статические языки предотвращают такие ошибки, но, конечно, степень предотвращения зависит от мощности системы типов.


Статические и динамические языки построены на фундаментально разных идеях о корректности программ. В динамическом языке "a" + 1 это корректная программа: код будет запущен и появится ошибка в среде исполнения. Однако, в большинстве статически типизированных языков выражение "a" + 1 — это не программа: она не будет скомпилирована и не будет запущена. Это некорректный код, так же, как набор случайных символов !&%^@*&%^@* — это некорректный код. Это дополнительное понятие о корректности и некорректности не имеет эквивалента в динамических языках.


Сильная и слабая типизация


Понятия "сильный" и "слабый" — очень неоднозначные. Вот некоторые примеры их использования:


  • Иногда "сильный" означает "статический".
    Тут все просто, но лучше использовать термин "статический", потому что большинство используют и понимают его.


  • Иногда "сильный" означает "не делает неявное преобразование типов".
    Например, JavaScript позволяет написать "a" + 1, что можно назвать "слабой типизацией". Но почти все языки предоставляют тот или иной уровень неявного преобразования, которое позволяет автоматически переходить от целых чисел к числам с плавающей запятой вроде 1 + 1.1. В реальности, большинство людей используют слово "сильный" для определения границы между приемлемым и неприемлемым преобразованием. Нет какой-то общепринятой границы, они все неточные и зависят от мнения конкретного человека.


  • Иногда "сильный" означает, что невозможно обойти строгие правила типизации в языке.


  • Иногда "сильный" означает безопасный для памяти (memory-safe).
    Си — это пример небезопасного для памяти языка. Если xs — это массив четырех чисел, то Си с радостью выполнит код xs[5] или xs[1000], возвращая какое-то значение из памяти, которая находится сразу за xs.

Давайте остановимся. Вот как некоторые языки отвечают этим определениям. Как можно заметить, только Haskell последовательно "сильный" по всем параметрам. Большинство языков не такие четкие.


Язык Статический? Неявные преобразования? Строгие правила? Безопасный для памяти?
C Сильный Когда как Слабый Слабый
Java Сильный Когда как Сильный Сильный
Haskell Сильный Сильный Сильный Сильный
Python Слабый Когда как Слабый Сильный
JavaScript Слабый Слабый Слабый Сильный

("Когда как" в колонке "Неявные преобразования" означает, что разделение между сильным и слабым зависит от того, какие преобразования мы считаем приемлемыми).


Зачастую термины "сильный" и "слабый" относятся к неопределенной комбинации разных определений выше, и других, не показанных здесь определений. Весь этот беспорядок делает слова "сильный" и "слабый" практически бессмысленными. Когда хочется использовать эти термины, то лучше описать, что конкретно имеется ввиду. Например, можно сказать, что "JavaScript возвращает значение, когда складывается строка с числом, но Python возвращает ошибку". В таком случае мы не будем тратить свои силы на попытки прийти к соглашению о множестве значений слова "сильный". Или, еще хуже: придем к неразрешенному непониманию из-за терминологии.


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


Как написал Крис Смит:


Сильная типизация: Система типов, которую я люблю и с которой мне комфортно.

Слабая типизация: Система типов, которая беспокоит меня или с которой мне не комфортно.

Постепенная типизация (gradual typing)


Можно ли добавить статические типы в динамические языки? В некоторых случаях — да. В других это сложно или невозможно. Самая очевидная проблема — это eval и другие похожие возможности динамических языков. Выполнение 1 + eval("2") в Python дает 3. Но что даст 1 + eval(read_from_the_network())? Это зависит от того, что в сети на момент выполнения. Если получим число, то выражение корректно. Если строку, то нет. Невозможно узнать до запуска, так что невозможно анализировать тип статически.


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


Значения типа Any не ограничены ничем, так что исчезает возможность системы типов помогать нам в коде с eval. Языки, в которых есть и eval и система типов, должны отказываться от безопасности типов при каждом использовании eval.


В некоторых языках есть опциональная или постепенная типизация (gradual typing): они динамические по умолчанию, но позволяют добавлять некоторые статические аннотации. В Python недавно добавили опциональные типы; TypeScript — это надстройка над JavaScript, в котором есть опциональные типы; Flow производит статический анализ старого доброго кода на JavaScript.


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


Компиляция статически типизированного кода


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


Компиляторы статических языков обычно могут генерировать более быстрый код, чем компиляторы динамических. Например, если компилятор знает, что функция add принимает целые числа, то он может использовать нативную инструкцию ADD центрального процессора. Динамический язык будет проверять тип при выполнении, выбирая один из множества функций add в зависимости от типов (складываем integers или floats или склеиваем строки или, может быть, списки?) Или нужно решить, что возникла ошибка и типы не соответствуют друг другу. Все эти проверки занимают время. В динамических языках используются разные трюки для оптимизации, например JIT-компиляция (just-in-time), где код перекомпилируется при выполнении после получения всей необходимой о типах информации. Однако, никакой динамический язык не может сравниться по скоростью с аккуратно написанным статическим кодом на языке вроде Rust.


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


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


Сторонники динамических языков указывают на то, что на таких языках, кажется, легче писать код. Это определенно справедливо для некоторых видов кода, который мы время от времени пишем, как, например, тот код с eval. Это спорное решение для регулярной работы, и здесь имеет смысл вспомнить неопределенное слово "легко". Рич Хики отлично рассказал про слово "легко", и его связь со словом "просто". Посмотрев этот доклад вы поймете, что не легко правильно использовать слово "легко". Опасайтесь "легкости".


Плюсы и минусы статических и динамических систем типизации все еще плохо изучены, но они определенно зависят от языка и конкретной решаемой задачи.


JavaScript пытается продолжить работу, даже если это означает бессмысленную конвертацию (вроде "a" + 1, дающее "a1"). Python в свою очередь старается быть консервативным и часто возвращает ошибки, как в случае с "a" + 1.


Существуют разные подходы с разными уровнями безопасности, но Python и JavaScript оба являются динамически типизированными языками.


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


Haskell же не позволит сложить integer и float без явного преобразования перед этим. Си и Haskell оба являются статически типизированными, не смотря на такие большие отличия.


Есть множество вариаций динамических и статических языков. Любое безоговорочное высказывание вида "статические языки лучше, чем динамические, когда дело касается Х" — это почти гарантированно ерунда. Это может быть правдой в случае конкретных языков, но тогда лучше сказать "Haskell лучше, чем Python когда дело касается Х".


Разнообразие статических систем типизации


Давайте взглянем на два знаменитых примера статически типизированных языков: Go и Haskell. В системе типизации Go нет обобщенных типов, типов с "параметрами" от других типов. Например, можно создать свой тип для списков MyList, который может хранить любые нужные нам данные. Мы хотим иметь возможность создавать MyList целых чисел, MyList строк и так далее, не меняя исходный код MyList. Компилятор должен следить за типизацией: если есть MyList целых чисел, и мы случайно добавляем туда строку, то компилятор должен отклонить программу.


Go специально был спроектирован таким образом, чтобы невозможно было задавать типы вроде MyList. Лучшее, что возможно сделать, это создать MyList "пустых интерфейсов": MyList может содержать объекты, но компилятор просто не знает их тип. Когда мы достаем объекты из MyList, нам нужно сообщить компилятору их тип. Если мы говорим "Я достаю строку", но в реальности значение — это число, то будет ошибка исполнения, как в случае с динамическими языками.


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


Теперь давайте сравним с Haskell, который обладает очень мощной системой типов. Если задать тип MyList, то тип "списка чисел" это просто MyList Integer. Haskell не даст нам случайно добавить строку в список, и удостоверится, что мы не положим элемент из списка в строковую переменную.


Haskell может выражать намного более сложные идеи напрямую типами. Например, Num a => MyList a означает "MyList значений, которые относятся к одному типу чисел". Это может быть список integer'ов, float'ов или десятичных чисел с фиксированной точностью, но это определенно никогда не будет списком строк, что проверяется при компиляции.


Можно написать функцию add, которая работает с любыми численными типами. У этой функции будет тип Num a => (a -> a -> a). Это означает:


  • a может быть любым численным типом (Num a =>).
  • Функция принимает два аргумента типа a и возвращает тип a (a -> a -> a).

Последний пример. Если тип функции это String -> String, то она принимает строку и возвращает строку. Но если это String -> IO String, то она также совершает какой-то ввод/вывод. Это может быть обращение к диску, к сети, чтение из терминала и так далее.


Если у функции в типе нет IO, то мы знаем, что она не совершает никаких операций ввода/вывода. В веб-приложении, к примеру, можно понять, изменяет ли функция базу данных, просто взглянув на ее тип. Никакие динамические и почти никакие статические языки не способы на такое. Это особенность языков с самой мощной системой типизации.


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


Сравните эту мощность с Go, который не способен выразить простую идею MyList, не говоря уже о "функции, которая принимает два аргумента, и они оба численные и одного типа, и которая делает ввод/вывод".


Подход Go упрощает написание инструментов для программирования на Go (в частности, реализация компилятора может быть простой). К тому же, требуется изучить меньше концепций. Как эти преимущества сравнимы со значительными ограничениями — субъективный вопрос. Однако, нельзя поспорить, что Haskell сложнее изучить, чем Go, и что система типов в Haskell намного мощнее, и что Haskell может предотвратить намного больше типов багов при компиляции.


Go и Haskell настолько разные языки, что их группировка в один класс "статических языков" может вводить в заблуждение, не смотря на то, что термин используется корректно. Если сравнивать практические преимущества безопасности, то Go ближе к динамических языкам, нежели к Haskell'у.


С другой стороны, некоторые динамические языки безопаснее, чем некоторые статические языки. (Python в целом считается намного безопаснее, чем Си). Когда хочется делать обобщения о статических или динамических языках как группах, то не забывайте об огромном количестве отличий между языками.


Конкретные примеры отличия в возможностях систем типизации


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


В Go можно сказать "функция add принимает два integer'а и возвращает integer":


func add(x int, y int) int {
    return x + y
}

В Haskell можно сказать "функция принимает любой численный тип и возвращает число того же типа":


f :: Num a => a -> a -> a
add x y = x + y

В Idris можно сказать "функция принимает два integer'а и возвращает integer, но первый аргумент должен быть меньше второго аргумента":


add : (x : Nat) -> (y : Nat) -> {auto smaller : LT x y} -> Nat
add x y = x + y

Если попытаться вызвать функцию add 2 1, где первый аргумент больше второго, то компилятор отклонит программу во время компиляции. Невозможно написать программу, где первый аргумент больше второго. Редкий язык обладает такой возможностью. В большинстве языков такая проверка происходит при выполнении: мы бы написали что-то вроде if x >= y: raise SomeError().


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


Системы типизации некоторых статических языков


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


  • C (1972), Go (2009): Эти системы совсем не мощные, без поддержки обобщенных типов. Невозможно задать тип MyList, который бы означал "список целых чисел", "список строк" и т.д. Вместо этого придется делать "список необозначенных значений". Программист должен вручную сообщать "это список строк" каждый раз, когда строка извлекается из списка, и это может привести к ошибке при исполнении.
  • Java (1995), C# (2000): Оба языка поддерживают обобщенные типы, так что можно сказать MyList<String> и получить список строк, о котором компилятор знает и может следить за соблюдением правил типов. Элементы из списка будут обладать типом String, компилятор будет форсировать правила при компиляции как обычно, так что ошибки при исполнении менее вероятны.
  • Haskell (1990), Rust (2010), Swift (2014): Все эти языки обладают несколькими продвинутыми возможностями, в том числе обобщенными типами, алгебраическими типами данных (ADTs), и классами типов или чем-то похожим (типы классов, признаки (traits) и протоколы, соответственно). Rust и Swift более популярны, чем Haskell, и их продвигают крупные организации (Mozilla и Apple, соответственно).
  • Agda (2007), Idris (2011): Эти языки поддерживают зависимые типы, позволяя создавать типы вроде "функция, которая принимает два целых числа х и y, где y больше, чем x". Даже ограничение "y больше, чем x" форсируется при компиляции. При выполнении y никогда не будет меньше или равно x, что бы ни случилось. Очень тонкие, но важные свойства системы могут быть проверены статически в этих языках. Их изучает очень мало программистов, но эти языки вызывают у них огромный энтузиазм.

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


Группа два (Java и C#) — это мэйнстримовые языки, зрелые и широко используемые.


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


Группа четыре (Idris and Agda) далеки от мэйнстрима, но это может измениться со временем. Языки группы три были далеко от мэйнстрима еще десять лет назад.

Поделиться с друзьями
-->

Комментарии (84)


  1. Iceg
    25.08.2016 11:54
    -3

    Интересно, что о приведении типов сказано примерно ничего.


  1. Mutineer
    25.08.2016 12:11
    +1

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


    1. lair
      25.08.2016 12:16
      +7

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


    1. meduzik
      25.08.2016 12:57
      +8

      Компилятор в языках с зависимыми типами проверяет не сами соотношения между параметрами, а специальные объекты-утверждения. Скажем, тип «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;
      
      который нужен только компилятору для проверкт корректности, но никогда реально не используется в рантайме.

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


      1. meduzik
        25.08.2016 17:50

        Дополню себя и комментарий 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> >){
                       //Обработка ошибки
                   });
            }
        


    1. VoidEx
      25.08.2016 13:34
      +2

      Доказав это на этапе компиляции тем или иным способом.
      Например, для положительного числа x, пришедшего по сети, число x*2 + 1 всё равно больше, чем x, и такую пару можно передать без проверок.
      Если же про оба числа ничего неизвестно, то, понятно, надо сделать динамическую проверку руками до вызова функции. Например, так:

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


      см Изоморфизм Карри — Ховарда


      1. leon_nikitin
        25.08.2016 16:45

        И при этом компилятор уже видит, что в ветке «можно звать функцию» x и y удовлетворяют типу функции?


        1. VoidEx
          25.08.2016 16:59
          +1

          Более подробен комментарий выше моего. Суть в том, что благодаря упомянутому изоморфизму, утверждение «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».


  1. deniskreshikhin
    25.08.2016 13:19
    +1

    Тип — это коллекция возможных значений.

    Хм, интересно есть хотя бы один язык в котором это действительно так?


    1. PavelMSTU
      25.08.2016 14:26

      Возможно и есть — мир огромен.;)

      freetonik перевел слово «collection» как «коллекция» а в данном случае нужно перевести как «совокупность».
      Это две большие разницы.


      1. freetonik
        25.08.2016 14:52

        В целом, тип данных это множество в математическом смысле. Так что считаю «коллекцию» нормальным переводом.


        1. deniskreshikhin
          25.08.2016 17:39

          Вообще-то даже с т.з. математики понятие типа не идентично понятию множества.


          На английской вики есть довольно аккуратное и исчерпывающее определение:


          In computer science and computer programming, a data type or simply type is a classification identifying one of various types of data, such as real, integer or Boolean, that determines the possible values for that type, the operations that can be done on values of that type, the meaning of the data, and the way values of that type can be stored


    1. ForNeVeR
      25.08.2016 14:46

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


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

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


      1. deniskreshikhin
        25.08.2016 17:59

        Да, это довольно близко к определению из статьи)


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


        1. ForNeVeR
          25.08.2016 18:36

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


    1. AnutaU
      25.08.2016 14:50

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

      Не единственное, но вполне подходящее определение для типа, по-моему.


      1. deniskreshikhin
        25.08.2016 17:25

        Эм, ну есть языки где тип определяет поведение например:


        struct T{
            void Foo();
        }

        Т.е. нельзя сказать что тип T является коллекцией чего-либо в принципе.


        1. ForNeVeR
          25.08.2016 18:38

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


          1. deniskreshikhin
            25.08.2016 20:18

            Если быть точным, то множество термов типа T равномощно множеству термов типа void(). Но с другой стороны, равномощность можеств термов ничего не говорит о эквивалентности типов, т.к. натуральный и целые числа тоже равномощны.


            1. VoidEx
              25.08.2016 21:13

              Отчего ж не говорит. Вы можете построить изоморфизм между целыми и натуральными (0, 1, 2… <-> 0, 2, 4...; -1, -2 <-> 1, 3...). Я так понимаю, в HoTT ещё и применить аксиому унивалентности (A ? B) ? (A = B).


              1. deniskreshikhin
                25.08.2016 23:12

                В HoTT да, есть такое, но у Мартина-Лёфа такие типы не считаются эквивалентными.


    1. Danik-ik
      25.08.2016 16:47

      Да ладно Вам, тут объясняют, откуда дети берутся, а Вы к терминам придираетесь…


  1. Ogoun
    25.08.2016 13:25

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


  1. zartdinov
    25.08.2016 13:34

    В случаи c TypeScript можно напомнить про SoundScript, попытку инженеров гугла внести больше смысла в такую типизацию.


  1. NeoCode
    25.08.2016 14:04

    Я сторонник статической типизации.
    Динамическая типизация всегда может быть частью статической. Пример — упоминавшийся уже тип any, который как раз и представляет собой универсальный динамический тип. Если в статически типизированном языке с any использовать только any, то скорее всего получится динамически типизированная программа.
    Можно сделать и промежуточные универсальные типы — например тип number для любых чисел — целых, с плавающей точкой и любой величины.
    В тех же случаях когда программисту точно известно, что ему нужен int32 и ни что другое, зачем лишать его такой возможности?


    1. lair
      25.08.2016 14:06
      +3

      Динамическая типизация всегда может быть частью статической.

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


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

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


      1. VoidEx
        25.08.2016 14:42

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

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

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


        1. lair
          25.08.2016 14:50
          -1

          Да, но обобщить несложно, не так ли?

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


          1. VoidEx
            25.08.2016 14:54

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


            1. lair
              25.08.2016 15:02

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


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


              1. VoidEx
                25.08.2016 15:31

                Введение 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
                



                1. lair
                  25.08.2016 15:38
                  +1

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

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


                  1. VoidEx
                    25.08.2016 15:41

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

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

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

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


                    1. lair
                      25.08.2016 17:04
                      +1

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


                      1. VoidEx
                        25.08.2016 17:06

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


                        1. lair
                          25.08.2016 17:19

                          на типизацию это никак не повлияет, она как была статической, так и осталась,

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


                          1. VoidEx
                            25.08.2016 17:26

                            Явно — нет.

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

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

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

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


                            1. lair
                              25.08.2016 17:28

                              Да и в исходном комментарии вот это прозвучало якобы как возражение:

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


                              1. VoidEx
                                25.08.2016 17:30

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


                                1. lair
                                  25.08.2016 17:31

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


                            1. grossws
                              26.08.2016 12:19
                              +3

                              В Скале, например, есть 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 — нормальный статически типизированный язык.


    1. OnYourLips
      25.08.2016 14:43

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

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

      При этом сам язык не обязательно должен обладать статической типизацией: на примере PHP можно увидеть, что практически во всех популярных библиотеках типы определены через комментарии (phpdoc), и инструменты используют их для статического анализа.


  1. Shamov
    25.08.2016 14:59
    +2

    Про типизацию не очень интересно. Я уже давно сделал свой жизненный выбор в пользу статической.
    Но зато я обогатил свой лексикон новым выражением — «почти гарантированно».


  1. Riateche
    25.08.2016 15:35
    +1

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

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

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


    1. AnutaU
      26.08.2016 10:53

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

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

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

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

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


  1. easimonenko
    25.08.2016 17:54
    +1

    В третью группу языков нужно добавить более популярную Scala.


    1. ForNeVeR
      25.08.2016 18:40

      И ещё, наверное, F#?


      1. easimonenko
        25.08.2016 21:48

        Пожалуй, да. Сюда также можно отнести Caml и OCaml.


        1. ForNeVeR
          26.08.2016 05:10

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


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

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


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


  1. Sirikid
    25.08.2016 20:04
    +1

    Тот самый случай, когда комментарии интересней поста.
    Iceg приведение типов от лукавого.


  1. darth_dolphi
    26.08.2016 10:15

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


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

    type MyList []string


    1. lair
      26.08.2016 11:25
      +1

      … это список, не массив?


      1. grossws
        26.08.2016 12:27

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


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


        1. lair
          26.08.2016 12:29
          +1

          Вот и я о чем.


        1. darth_dolphi
          26.08.2016 12:56

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


          1. lair
            26.08.2016 13:03
            +2

            Какой тип возвращает list.Front().Value?


            1. darth_dolphi
              26.08.2016 13:13

              1. lair
                26.08.2016 13:15
                +3

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


              1. lair
                26.08.2016 13:21
                +3

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


                    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


                1. darth_dolphi
                  26.08.2016 16:33

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


                  1. lair
                    26.08.2016 16:38
                    +5

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

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


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

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


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

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


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


                    1. darth_dolphi
                      26.08.2016 17:04
                      -1

                      Насчет удобства, возможно вы правы, чем меньше кода тем лучше.

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

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

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


                      1. lair
                        26.08.2016 17:06
                        +2

                        Насчет удобства, возможно вы правы, чем меньше кода тем лучше.

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


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

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


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

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


                        1. darth_dolphi
                          26.08.2016 17:15

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

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


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

                          не умеет .sort()?


                          1. lair
                            26.08.2016 17:19

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

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


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

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


                          1. senia
                            27.08.2016 08:42
                            +1

                            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))
                            
                            


                      1. lair
                        26.08.2016 17:09

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

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


                        1. darth_dolphi
                          26.08.2016 17:19

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

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

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


                          1. lair
                            26.08.2016 17:20

                            не создает новый обьект?

                            Нет.


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

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


                            1. darth_dolphi
                              26.08.2016 17:36

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


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


                              1. lair
                                26.08.2016 17:42

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


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


                                1. darth_dolphi
                                  26.08.2016 17:46
                                  -1

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

                                      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);
                                       }
                                  


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


                                  1. lair
                                    26.08.2016 17:47
                                    +1

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


                            1. darth_dolphi
                              26.08.2016 17:42
                              -1

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


                              1. lair
                                26.08.2016 17:43
                                +1

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


                                1. darth_dolphi
                                  26.08.2016 18:00

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

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

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


                                  1. lair
                                    26.08.2016 18:03
                                    +2

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

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


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

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


                                    1. darth_dolphi
                                      26.08.2016 18:13
                                      -1

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

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

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


                                      1. lair
                                        26.08.2016 18:17
                                        +2

                                        Если рассматривать вопрос может он это или нет, то может.

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


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


  1. FireGM
    26.08.2016 10:15

    Возможно я не понял о чем речь, но

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

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


    1. lair
      26.08.2016 11:25

      … это список, не массив?


      1. darth_dolphi
        26.08.2016 12:36

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


        1. lair
          26.08.2016 12:39
          +4

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

          Да.


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

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


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

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


  1. G-M-A-X
    26.08.2016 10:29
    +1

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

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

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

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

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

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

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

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

    Это о слабая — сильная типизация…