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

57

Почему почти во всех современных языках программирования (Go, Rust, Kotlin, Swift, Scala, Nim, даже последняя версия Python) типы всегда идут после имени переменной в объявлении переменной, а не раньше?

Почему x: int = 42и нет int x = 42?
Разве последнее не более читабельно, чем первое?
Это просто тенденция или есть какие-то действительно значимые причины этого решения?

Андре Поликанин
источник
3
Да, это дурак, хорошо. Хотя это относится конкретно к языку котлина, вопрос (и его ответы) относятся ко всем языкам, имеющим такую ​​практику.
Роберт Харви
2
Извините, ребята, я видел вопрос о Kotlin, когда гуглил, но я переспросил его, чтобы не получить ответы типа «Потому что команда разработчиков Kotlin (Go, Rust, ...) решила так». Меня интересовал более распространенный ответ: почему это становится своего рода эпидемией?
Андре Поликанин
2
Hihi, так что Delphi, который является (Object) Pascal и имеет типы после имен переменных с момента своего создания, еще в темные времена прошлого века, снова стал современным;) Кстати, читаемость сильно зависит от того, кем вы являетесь использовал к. Исходя из Delphi, C # с его типом перед именем var, довольно долго заставлял меня биться головой.
Марьян Венема

Ответы:

64

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

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

Этот аргумент становится особенно сильным , когда вы учитываете все необязательные модификаторы типа , что «не современный» язык , как C ++ имеет, например, *для указателей, &ссылок, const, volatileи так далее. Как только вы добавляете запятые для нескольких объявлений, вы начинаете получать некоторые действительно странные неясности, такие как int* a, b;отсутствие bуказателя.

Даже C ++ теперь поддерживает объявления типа справа auto x = int{ 4 };, и у него есть некоторые преимущества .

Ixrec
источник
2
+1 за признание наличия таких обязательных ключевых слов, varкоторые обеспечивают большую часть упрощения синтаксического анализа.
8bittree
2
Разве существование varключевого слова не противоречит цели обозначения «имя-имя»? Я не вижу преимущество написания var userInput: String = getUserInput()над String userInput = getUserInput(). Преимущество будет уместным только в том случае, если userInput = getUserInput()это также разрешено, но это подразумевает неявное объявление переменной области действия, что я нахожу довольно отвратительным.
КДБ
2
@kdb в таких языках, как C # и C ++, это было бы var userInput = getUserInput()или auto userInput = getUserInput(), компилятор знает, что getUserInput()возвращает, Stringпоэтому вам не нужно указывать его с помощью ключевого слова deduction type. userInput = getUserInput()конечно использовать перед объявлением.
Уэс Толеман
32

Почему почти во всех современных языках программирования (Go, Rust, Kotlin, Swift, Scala, Nim и даже последняя версия Python) типы всегда идут после объявления переменной, а не раньше?

Ваша предпосылка ошибочна по двум направлениям:

  • Есть новые языки программирования, которые имеют тип перед идентификатором. C♯, D или Цейлон, например.
  • Наличие типа после идентификатора не является новым явлением, оно восходит, по крайней мере, к Паскалю (разработанному в 1968–1969 гг., Выпущенному в 1970 г.), но фактически использовалось в математической теории типов, которая начинается ~ 1902 г. Он также использовался в ML (1973), CLU (1974), Hope (1970-е), Modula-2 (1977–1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon. (1986), Modula-3 (1986–1988) и Haskell (1989).

Pascal, ML, CLU, Modula-2 и Miranda были очень влиятельными языками, поэтому неудивительно, что этот стиль объявлений типов оставался популярным.

Почему x: int = 42и нет int x = 42? Разве последнее не более читабельно, чем первое?

Читаемость - это вопрос знакомства. Лично я считаю, что китайский нечитаем, но, видимо, китайцы этого не делают. Изучив Паскаль в школе, поболтав с Эйфелевой, F♯, Haskell и Scala, и посмотрев на TypeScript, Fortress, Go, Rust, Kotlin, Идрис, Фреге, Agda, ML, Ocaml,…, для меня это выглядит совершенно естественно ,

Это просто тенденция или есть какие-то действительно значимые причины такого решения?

Если это тенденция, она довольно устойчивая: как я уже говорил, в математике она насчитывает сто лет.

Одним из основных преимуществ наличия типа после идентификатора является то, что он легко опускается, если вы хотите, чтобы он был выведен. Если ваши объявления выглядят так:

val i: Int = 10

Тогда тривиально опустить тип и вывести его следующим образом:

val i = 10

Принимая во внимание, что если тип предшествует идентификатору, как это:

Int i = 10

Затем парсеру становится трудно отличить выражение от объявления:

i = 10

Решение, которое обычно предлагают разработчики языка, состоит в том, чтобы ввести ключевое слово «Я не хочу писать тип», которое должно быть написано вместо типа:

var  i = 10; // C♯
auto i = 10; // C++

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

(И давайте даже не будем говорить о типах указателей на функции в C.)

Разработчики нескольких из вышеупомянутых языков взвесили эту тему:

  • Go FAQ (см. Также: Синтаксис декларации Go ):

    Почему декларации задом наперед?

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

    int* a, b;
    

    объявляется aуказателем, но не указывается b; на ходу

    var a, b *int
    

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

    var a uint64 = 1
    

    имеет тот же эффект, что и

    a := uint64(1)
    

    Синтаксический анализ также упрощается благодаря наличию отдельной грамматики для типов, которая не является просто грамматикой выражения; ключевые слова, такие как funcи chanсохранить вещи

  • Kotlin FAQ :

    Почему объявления типа справа?

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

  • Программирование в Scala :

    Основное отличие от Java касается синтаксиса для аннотаций типов - это " variable: Type" вместо " Type variable" в Java. Синтаксис типа постфикса в Scala напоминает Pascal, Modula-2 или Eiffel. Основная причина этого отклонения связана с выводом типа, который часто позволяет опустить тип переменной или тип возврата метода. С помощью variable: Typeсинтаксиса " " это легко - просто пропустите двоеточие и тип. Но в " Type variable" синтаксисе в стиле C " вы не можете просто пропустить тип - не было бы маркера, чтобы начать определение больше. Вам понадобится какое-нибудь альтернативное ключевое слово, чтобы заполнить пропущенный тип ( varдля этой цели используется C some 3.0, который делает какой-то вывод типа ). Такое альтернативное ключевое слово кажется более специальным и менее регулярным, чем подход Scala.

Примечание: разработчики Ceylon также задокументировали, почему они используют синтаксис префиксного типа :

Префикс вместо постфиксных аннотаций

Почему вы следуете за C и Java, помещая сначала аннотации типов, а не Pascal и ML, помещая их после имени объявления?

Потому что мы думаем это:

shared Float e = ....
shared Float log(Float b, Float x) { ... }

Просто намного легче читать, чем это:

shared value e: Float = .... 
shared function log(b: Float, x: Float): Float { ... }

И мы просто не понимаем, как кто-то мог думать иначе!

Лично я нахожу их «аргумент» гораздо менее убедительным, чем другие.

Йорг Миттаг
источник
4
Кажется , способность выйти из типа и до сих пор просто синтаксический предоставляется путем добавления var, auto, letили тому подобное, а не с какой стороны от переменной объявления типа включена. var Int x = 5или даже Int var x = 5оба могут быть сокращены до var x = 5.
8bittree
5
Хотя мне нравится, что он одинаково читабелен, как только вы привыкнете к нему, и необязательный аргумент типа является сильным, я хотел бы отметить, что IDE не может автоматически предлагать имена переменных на основе типа. Я скучаю по этому, когда я пишу TypeScript.
Пьер Генри
"Your premise is flawed on two fronts" Все они новее, чем C # или D.
Дет
3
«Но на самом деле это не имеет особого смысла: вам нужно явно написать тип, который говорит, что вы не пишете тип». Ну, версия типа «на правильном» точно такая же в вашем примере ... Также я не согласна с тем, что это не имеет смысла. Это имеет такой же смысл, как и funcв Go.
Сопель
4

Кстати, Паскаль делает это тоже, и это не новый язык. Это был академический язык, разработанный с нуля.

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

C # и Java происходят от C, поэтому они должны уважать «уровень техники», чтобы не перепутать опытного программиста.

Мартин Маат
источник
3
Ада тоже так поступает, но Ада, безусловно, не «академический язык».
Джон Р. Штром
Ада сделала это, потому что она тяжело извлекалась из Паскаля и Модулы 2.
Gort the Robot
2
@StevenBurnap, быстрый поиск показывает, что первая книга Вирта о Модуле-2 вышла в середине 1982 года. Ада находилась в стадии разработки за несколько лет до этого (насколько я помню, DoD1 был в 1977 году или около того) и стандартизировалась как стандарт ANSI и MIL-STD в 1983 году. Все четыре команды, представившие кандидатов на Аду, приняли PASCAL. как их отправные точки. (Bell Labs был приглашен Министерством обороны США представить кандидатский язык на основе C. Они возразили, заявив, что C тогда не было и никогда не будет достаточно надежным для критически важного программного обеспечения DoD.)
Джон Р. Штром,
Должно быть, я не помню. Я думал, что Вирт был вовлечен в Аду.
Gort the Robot
Паскаль начался академический язык, но к концу 90 - х годов он был на грани того , чтобы быть языком для написания Windows , приложения в помощью Delphi, то есть до тех пор , Борланд не прыгнул акула с ценообразованием и оставил дверь открытой для Java и C # в иди и укради приз. Вы все равно увидите тонну Delphi в старом мире приложений для Windows.
Шейн
1

Почему x: int = 42и нет int x = 42? Разве последнее не более читабельно, чем первое?

В таком простом примере нет особой разницы, но давайте сделаем его немного сложнее: int* a, b;

Это действительное, правильное объявление в C, но оно не делает то, что выглядит интуитивно, как должно. Похоже, мы объявляем две переменные типа int*, но на самом деле мы объявляем одну int*и одну int.

Если ваш язык помещает тип после имени (имен) переменных в своих объявлениях, с этой проблемой невозможно столкнуться.

Мейсон Уилер
источник
4
Я не уверен, почему перемещение объявления типа после повлияет на это. Это x, y *int;два *intили один *intи один int? Создание int*или *intодин токен вместо двух может решить эту проблему, или использование дополнительного синтаксического элемента, такого как :, который может работать в любом направлении.
8bittree
@ 8bittree Каждый знакомый мне язык, использующий объявления имени, использует дополнительный токен, например :или as, между именем и типом.
Мейсон Уилер
2
А в Go x, y *intозначает «объявить две переменные, x и y, с указателем типа на int».
Ватин
10
C # использует синтаксис префикса, но он обрабатывает int[] x, yкак два массива. Это просто глупый синтаксис C, который рассматривает эти модификаторы как унарные операторы в переменной (и не менее, как префикс и постфикс), что вызывает проблемы.
CodesInChaos,