Существует два распространенных варианта синтаксиса
"сначала тип" используется например в C/C++/Java/C#/D
int x;
и "сначала объект" используется например в Pascal/Go/Rust/Swift/Scala (с разными вариациями — двоеточия перед типом и т.п., еще важный момент — часто используются ключевые слова в начале, вроде var и let)
x int;
Современные языки имеют тенденцию ко второму варианту.
Почему? У него есть какое-то фундаментальное преимущество перед первым?
Про т.н. "most vexing parse" я знаю, это ошибка дизайна именно С/С++ и она не имеет прямого отношения к тому что тип идет первым, это скорее проблема того что функция начинается не с ключевого слова (func, fn) а с возвращаемого типа. Сложность с синтаксисом указателей на функции тоже с этим связана.
Все-же интересно, какие преимущества и недостатки есть у обоих способов объявления?
Одерски выбирал, потому что по его мнению имя важней типа и то что хорошо работало в си с короткими именами типов, спрячет его в скале за развесистой декларацией типа
Здравствуйте, x-code, Вы писали:
XC>Современные языки имеют тенденцию ко второму варианту. XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?
Да. Достаточно попытаться распарсить какой-нибудь int *(*fp)(float*[]) и описать, как это делается (выворачивать в уме все эти конструкции наизнанку).
Да даже typedef int *pint; реально надо прочитать как "pint = type pointer to int" и только после этого начинается понимание (даже если это в простом случае делается тривиально).
Это общее место учебников по C/C++ — при парсинге разворачиваешь эти описания задом наперёд.
XC>Про т.н. "most vexing parse" я знаю, это ошибка дизайна именно С/С++ и она не имеет прямого отношения к тому что тип идет первым, это скорее проблема того что функция начинается не с ключевого слова (func, fn) а с возвращаемого типа.
Совсем прямого — не имеет. Непрямое — имеет — что из-за кривого порядка var int x становятся неадекватными.
XC> Сложность с синтаксисом указателей на функции тоже с этим связана.
Здравствуйте, Pzz, Вы писали:
Pzz>Мне как-то попадалось объяснение, почему в Go сделано по второму варианту. Не сути, не ссылки я не помню, поищи сам. Pzz>Если учесть, что Go и C сделали примерно одни и те же люди, то их взгляд на вещи имеет значение.
https://blog.golang.org/declaration-syntax
Там в основном упор на сложность объявления типов указателей на функции. Это правда, но это решается введением ключевого слова для функций и никакого отношения не имеет к тому, что в начале при объявлении — тип или объект.
Здравствуйте, netch80, Вы писали:
N>Да. Достаточно попытаться распарсить какой-нибудь int *(*fp)(float*[]) и описать, как это делается (выворачивать в уме все эти конструкции наизнанку).
Это имеет отношение к тому что тип вначале? Или это следствие того что для функции нет ключевого слова?
(func(float*[]) int*)* fp;
N>Да даже typedef int *pint; реально надо прочитать как "pint = type pointer to int" и только после этого начинается понимание (даже если это в простом случае делается тривиально).
typedef вообще отдельная конструкция.
в С++ ввели using, правда странный синтаксис указателей на функции оставили, причем он еще и отличается от того что в typedef
typedef float (*func_ptr)(int);
using func_ptr = float (*)(int);
N>Совсем прямого — не имеет. Непрямое — имеет — что из-за кривого порядка var int x становятся неадекватными.
Здравствуйте, torvic, Вы писали:
T>Одерски выбирал, потому что по его мнению имя важней типа и то что хорошо работало в си с короткими именами типов, спрячет его в скале за развесистой декларацией типа
А насколько удобен синтаксис конструкторов с такой нотацией? Я не знаю Scala, но вот допустим на обычном си-подобном языке
В первом варианте я пишу: User | и мне IDE уже подсказывает имя переменной user. Которое я и выберу, возможно с тем или иным суффиксом или префиксом. Т.к. очень часто имена переменных используют название типа. Во втором случае это уже так не работает.
Ещё один момент: если я не хочу указывать тип, я пишу var someVariable = .... Т.е. я заменяю название типа на var. Ну или на auto, let или другую подобную конструкцию. Это смотрится очень естественно и логично. Во втором варианте приходится писать этот var всегда, хоть я указываю тип, хоть нет. В этом в принципе есть и свои плюсы (все объявления переменных сразу выделяются) и минусы (лишняя писанина).
Здравствуйте, x-code, Вы писали:
T>>Одерски выбирал, потому что по его мнению имя важней типа и то что хорошо работало в си с короткими именами типов, спрячет его в скале за развесистой декларацией типа
XC>А насколько удобен синтаксис конструкторов с такой нотацией? Я не знаю Scala, но вот допустим на обычном си-подобном языке XC>
Foo obj(1,2,"hello")
; XC>или XC>
Foo obj = new Foo(1,2,"hello")
;
XC>если всё это развернуть, то что получится? XC>
obj(1,2,"hello") Foo;
или как-то так?
val obj1: Foo = new Foo(1, 2, "hello");
val obj2 = new Foo(1, 2, "hello"); // с автовыводом типа
Здравствуйте, x-code, Вы писали:
XC>Современные языки имеют тенденцию ко второму варианту. XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?
Думаю потому что современные ЯП осознали, что часто можно выполнить вывод типа из контекста.
let s = 42
// val s : int = 42
При этом двоеточие с типом читаю как уточнение типа переменной
let say (name : string) = Console.WriteLine("Hello, " + name)
Далее логически вытекает приведение типов
type Person(name) =
class
do printfn "Person name is %s" name
end
type Worker(name) =
class
inherit Person(name)
do printfn "Worker name is %s" name
end
let person = Person("Bob")
let worker = Worker("Steve")
let steve = worker :> Person // успешное приведение к предку (вверх). ошибки не будет
try
let bob = person :?> Worker
None
with :? System.InvalidCastException as e ->
printfn $"Ошибка приведение к потомку(вниз) {e.Message}"None
Здравствуйте, x-code, Вы писали:
N>>Да. Достаточно попытаться распарсить какой-нибудь int *(*fp)(float*[]) и описать, как это делается (выворачивать в уме все эти конструкции наизнанку).
XC>Это имеет отношение к тому что тип вначале? Или это следствие того что для функции нет ключевого слова? XC>
(func(float*[]) int*)* fp;
Имеет к тому, что тип вначале.
Нет, не следствие.
N>>Да даже typedef int *pint; реально надо прочитать как "pint = type pointer to int" и только после этого начинается понимание (даже если это в простом случае делается тривиально).
XC>typedef вообще отдельная конструкция.
Она следует общему тренду языка. Если бы у него был тип справа, то было бы как во втором примере.
XC>в С++ ввели using, правда странный синтаксис указателей на функции оставили, причем он еще и отличается от того что в typedef XC>
Это следствие того, что не смогли исправить до конца.
N>>Совсем прямого — не имеет. Непрямое — имеет — что из-за кривого порядка var int x становятся неадекватными.
XC>Вот тут непонятно, что за "var int x"?
Это если ты потребуешь добавлять var или func, но не сдвинешь тип вправо от переменной.
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, x-code, Вы писали:
XC>>"сначала объект" используется например в Pascal/Go/Rust/Swift/Scala
Q>Главное, что такая нотация используется в математике, например: https://en.wikipedia.org/wiki/Simply_typed_lambda_calculus Q>Оттуда уже её впитали все остальные языки (кроме Си-подобных).
C это тоже не первый источник, до этого был Algol, а до него Fortran.
В Fortran, похоже, источником явилось то, что писали
DIMENSION A(10,10)
для определения, что переменная — массив, а не скаляр, а дальше когда стали явно задавать типы — сделали по аналогии:
Здравствуйте, x-code, Вы писали:
XC>Современные языки имеют тенденцию ко второму варианту. XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?
По-моему, фундаментальных никаких нету. Просто в теории типов издавна было "x : T", потом так же было в языках ML семейства еще с 70х годов. А теперь авторы языков наконец открыли учебники/статьи и прониклись.
Кому как больше нравится, предпочтения чаще всего эстетические. К опциональности указания типов это отношения не имеет.
Что мне лично не очень нравится в нотации "тип справа":
x : T = v
выглядит как "х имеет тип (Т = v)". При том, что в некоторых языках вроде Idris "a = b" это вполне осмысленный тип.
И вообще, почему мы пишем T = v, когда имеем в виду x = v?
И еще лишний символ :, код аж на целую букву длиннее.
Когда типы слева, делать с функциями хтонический ужас как у Си никто не заставляет, конечно.
Я на работе занимаюсь созданием ЯП, и когда добавлял туда типы, сделал типы слева. Получилось довольно складно, имхо:
int x = 42
x = 42 // тип можно и не указывать, он будет выведен
(a,b) => a+b // лямбда, все типы выводятся
(int a, int b) => a+b // лямбда с указанием типов аргументов
(a,b) => int: a+b // лямбда с указанием типа результата
f(int a, int b) => a+b // определение функции. заметьте, как легко
f(a, b) => int: a+b // превращать лямбду в ф-ю и обратно
inc(x) => int: x+1 //
(int) -> int g = inc // переменная функционального типа, нет проблем его иметь слева
map((#a) -> #b f, [#a] xs) => ... // тут аргумент функ. типа, тоже все ок