Re[2]: Синтаксис объявления переменных
От: x-code  
Дата: 05.04.21 17:56
Оценка: +1
Здравствуйте, D. Mon, Вы писали:

DM>Я на работе занимаюсь созданием ЯП, и когда добавлял туда типы, сделал типы слева. Получилось довольно складно, имхо:


У вас указателей нет. Я проанализировал и понял что основная проблема — наличие всяких звездочек и прочих спецсимволов.

Вот например код вида
ID1 * ID2;

это что? Это может быть и объявление типа
int * ptr;

и умножение двух чисел или объектов с перегруженным оператором
x * y;

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

Мне тоже нравится когда тип слева, но с другой стороны я прекрасно понимаю аргументы высказавшихся здесь в пользу типа справа. Просто хочется более систематического изложения всех этих аргументов, с подробным рассмотрением всех случаев.
Re[3]: Синтаксис объявления переменных
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 05.04.21 18:36
Оценка: 1 (1) +3 :)
Здравствуйте, x-code, Вы писали:

XC>У вас указателей нет. Я проанализировал и понял что основная проблема — наличие всяких звездочек и прочих спецсимволов.


Так опять же никто не заставляет синтаксис для типа указателей делать как в Си. Делайте "ref int" как в ML или Rust'e, волосы будут шелковистыми. "ID1 * ID2;" это проблема вида "суну себе в колесо палку". Кто заставлял-то?
Re: Синтаксис объявления переменных
От: glh Россия  
Дата: 05.04.21 21:26
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Существует два распространенных варианта синтаксиса

XC>"сначала тип" используется например в C/C++/Java/C#/D
XC>
int x;

XC>и "сначала объект" используется например в Pascal/Go/Rust/Swift/Scala (с разными вариациями — двоеточия перед типом и т.п., еще важный момент — часто используются ключевые слова в начале, вроде var и let)
XC>
x int;


XC>Современные языки имеют тенденцию ко второму варианту.

XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?

XC>Про т.н. "most vexing parse" я знаю, это ошибка дизайна именно С/С++ и она не имеет прямого отношения к тому что тип идет первым, это скорее проблема того что функция начинается не с ключевого слова (func, fn) а с возвращаемого типа. Сложность с синтаксисом указателей на функции тоже с этим связана.


XC>Все-же интересно, какие преимущества и недостатки есть у обоих способов объявления?


Больше связано с устройством компилятора и способом помещения объекта в его таблицах/ветвлениях парсера.
"C"/type first-компиляторы более сложные именно в частности потому, что требуют "возврата".
Успехов!
C уважением, Алексей.
------------------------------------------------
Хороших %s не бывает — бывает не худший вариант.
Re[4]: Синтаксис объявления переменных
От: Sharov Россия  
Дата: 06.04.21 10:10
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM> Кто заставлял-то?


Возможности языка, если так можно сделать, то почему бы не сделать?
Кодом людям нужно помогать!
Re[2]: Синтаксис объявления переменных
От: Sharov Россия  
Дата: 06.04.21 10:12
Оценка:
Здравствуйте, glh, Вы писали:

glh>Больше связано с устройством компилятора и способом помещения объекта в его таблицах/ветвлениях парсера.


Не очень в теме, но какая разница для парсера x int или int x? В каком-то случае правила сложнее?
Кодом людям нужно помогать!
Re[5]: Синтаксис объявления переменных
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 06.04.21 11:05
Оценка: 1 (1) +1
Здравствуйте, Sharov, Вы писали:

S>Возможности языка, если так можно сделать, то почему бы не сделать?


Если автор языка создает на ровном месте без хороших на то причин суровые неоднозначности в грамматике, а потом героически их преодолевает, то с одной стороны он, конечно, герой, а с другой стороны дурак.
Re: Синтаксис объявления переменных
От: maxkar  
Дата: 06.04.21 15:17
Оценка: -1
Здравствуйте, x-code, Вы писали:

XC>Современные языки имеют тенденцию ко второму варианту.

XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?

Добавлю еще две причины к тому, что уже сказали.

Каррирование функций

Здесь хорошо видны корни ML-семейства, в котором все функции были каррироваными (т.е. от одного аргумента).
let magic a b c = a *. float_of_int (String.length b + c);;                   
magic 1.0 "test" 2;;

Здесь a — float, b — string, c — int. Вопрос — какой тип у magic? И здесь более естественно иметь float -> string -> int -> float вместо float <- int <- string <- float. С первым еще и удобно работать. Например, если применить magic к одному аргументу, получится (string -> int -> float). Можно и справа откусывать, но зачем?

Примерно то же мы имеем в Scala (и прочих функциональных языках):
scala> def magic(a: Float, b: String, c: Int): Float =
     |   a * (b.length() + c)
magic: (a: Float, b: String, c: Int)Float

scala> :t  (magic _)
(Float, String, Int) => Float

scala> :t (magic _).curried
Float => (String => (Int => Float))

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

Обобщенные функции

Я не видел ни одного хорошего решения для указания типов-параметров в языках с типами слева. Java:
class Test<A> {
  <B> B magic(A a, B b) {
    return b;
  }

  public static void main(String[]  args) {
    final Test<String> test = new Test<>();
    test.<Thread>magic(null, null);
  }
}

В классе параметры-типы идут после имени. В методе — до. В вызове метода параметры передаются перед названием метода. Еще лучше это выглядит с обобщенными (generic) конструкторами (ради справедливости, scala вообще не поддерживает обобщенные конструкторы):
class Genc<A> {
  <B> Genc(A a, B b) {
  }

  public static void main(String[] args) {
    new <Integer>Genc<String>("Hello", 42);
  }
}

Удачи такое читать

Ладно, Дарт:
class Test<A> {
  B magic<B>(B b) {
    return b;
  }
}

void main() {
  Test<String> s = new Test<String>();
  s.magic<BigInt>(null);
}

Уже лучше. Но в определении функции ссылка на тип-параметр используется до объявления параметра. И вроде бы мелочи, но ведь не логично:
class Test<A> {
  A magic<A>(A b) {
    return b;
  }
}

void main() {
  Test<String> s = new Test<String>();
  s.magic<BigInt>(BigInt.parse("123"));
}

Что это за приколы с lexical scope? По структуре программы magic вроде возвращает A уровня класса (внешний lexical scope). А по факту — A уровня метода. Сравните эти ужасы со scala:
class Test[A] {
  def magic[B](a: A, b: B): B = b
}

val t = new Test[Int]
t.magic[String](42, "Hi")

Порядок параметров в определении и вызове совпадают. Параметр типа определяется до его использования в определении метода. Плюс в теории типов удобно рассматривать типы как отдельную группу аргументов. Т.е. magic могла бы иметь тип вроде [_:B] => (A, B) => B. Но не имеет, потому что generic functions слишком быстро приводят к проблеме неразрешимости типов. Я плохо помню, что там конкретно становится невозможно проверить. Вроде бы уже простую эквивалентность двух типов вывести нельзя если есть generic function type.
Re[2]: Синтаксис объявления переменных
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 06.04.21 17:23
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Здесь a — float, b — string, c — int. Вопрос — какой тип у magic? И здесь более естественно иметь float -> string -> int -> float вместо float <- int <- string <- float. С первым еще и удобно работать. Например, если применить magic к одному аргументу, получится (string -> int -> float). Можно и справа откусывать, но зачем?


M>Примерно то же мы имеем в Scala (и прочих функциональных языках):

M>
scala> def magic(a: Float, b: String, c: Int): Float =
     |   a * (b.length() + c)
magic: (a: Float, b: String, c: Int)Float

scala> :t  (magic _)
(Float, String, Int) => Float

scala> :t (magic _).curried
Float => (String => (Int => Float))

M>Пример — это я из scala console скопировал вывод. Скобочки в последней строке можно при написании опустить, будет то же самое. Иметь тип справа удобно, не пришлось порядок менять.

Непонятно. Как связан способ записи функционального типа (где порядок типов аргументов соответствует порядку аргументов) и то, с какой стороны от переменной он окажется? Кто мешает тот же самый тип записать слева?
(Float, String, Int) => Float magic = ...


M>

Обобщенные функции

M>Я не видел ни одного хорошего решения для указания типов-параметров в языках с типами слева.

В джаве выглядит странно, да, но вариант из Дарта, Ди или того же С++ вовсе не плох.
Ну используем мы тип-параметр чуть левее, чем он упомянут как параметр, нет в этом ничего крамольного, все равно все на одной строчке.
class Test<A> {
  B magic<B>(B b) {


В некоторых языках типы-параметры и известные типы просто по-разному пишутся, так что специально перечислять их не нужно. В Окамле это 'aaa против bbb, в Хаскеле aaa против Bbb. У меня это #aaa против bbb.


M>Что это за приколы с lexical scope? По структуре программы magic вроде возвращает A уровня класса (внешний lexical scope). А по факту — A уровня метода. Сравните эти ужасы со scala:

class Test[A] {
  def magic[B](a: A, b: B): B = b
}

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

Но это мы уже про синтаксис объявления методов/функций заговорили, не про переменные (см. заголовок темы).

M>Порядок параметров в определении и вызове совпадают. Параметр типа определяется до его использования в определении метода. Плюс в теории типов удобно рассматривать типы как отдельную группу аргументов. Т.е. magic могла бы иметь тип вроде [_:B] => (A, B) => B. Но не имеет, потому что generic functions слишком быстро приводят к проблеме неразрешимости типов. Я плохо помню, что там конкретно становится невозможно проверить. Вроде бы уже простую эквивалентность двух типов вывести нельзя если есть generic function type.


Ну так то, что в угловых (С++, Дарт и пр.) или квадратных (Скала) скобках идет, это такой список "аргументов" и есть.
Тут самая обычная полиморфная функция, с такими ФЯ работают свободно с середины 70х, никаких проблем там нет. Нужно наворотить хотя бы rank-2 тип, чтобы начались трудности с выводом.
Re[2]: Синтаксис объявления переменных
От: x-code  
Дата: 06.04.21 20:58
Оценка: +1
Здравствуйте, maxkar, Вы писали:

M>

Каррирование функций

M>
scala>> def magic(a: Float, b: String, c: Int): Float =
M>     |   a * (b.length() + c)
M>magic: (a: Float, b: String, c: Int)Float

scala>> :t  (magic _)
M>(Float, String, Int) => Float

scala>> :t (magic _).curried
M>Float => (String => (Int => Float))
M>


А при чем тут тип слева/справа? Вот вариант на некотором псеводкоде с типом слева
def magic(Float a, String b, Int c): Float =  a * (b.length() + c)

Каррирование как было так и остается, порядок типов тот же.

M>

Обобщенные функции

M>Я не видел ни одного хорошего решения для указания типов-параметров в языках с типами слева. Java:

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

M>
M>class Test<A> {
M>  <B> B magic(A a, B b) {
M>    return b;
M>  }

M>  public static void main(String[]  args) {
M>    final Test<String> test = new Test<>();
M>    test.<Thread>magic(null, null);
M>  }
M>}
M>


Очевидно что этот синтаксис кривой. Вот вариант на некотором псеводкоде, выглядящий гораздо более понятно и унифицированно:
class Test<A> {
  func<B> magic(A a, B b) B {
    return b;
  }
}


Добавление ключевого слова func или fn для функций по аналогии с class и подобными словами (struct, union, enum...) значительно упрощает синтаксис функций. Но является ли такая аналогия полностью верной для переменных (начинать все с ключевых слов — auto, var, val, let...)?
Re: Синтаксис объявления переменных
От: elmal  
Дата: 07.04.21 08:28
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Все-же интересно, какие преимущества и недостатки есть у обоих способов объявления?

Собственно в последнее время статически типизированные языки предпочитают тип выводить из того, что стоит после присваивания, а переменные и константы объявлять как var и val соответственно. Это уже давно пришло и в Java и в C#, собственно как и в плюсы. И вот при таком объявлении ситуация, когда тип все таки желательно указать — в случае если тип справа, то получается более регулярный синтаксис. А если тип слева, то регулярность теряется, в результате читается все то слева, то справа, и читаемость кода ухудшается. Конкретно компиляторам пофиг, от синтаксиса особого усложнения нет, а вот для человека не пофиг.
Re[3]: Синтаксис объявления переменных
От: maxkar  
Дата: 07.04.21 16:32
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Я понял, вы кажется рассказываете про возвращемый тип слева/справа от списка аргументов функции, а вопрос был про тип слева/справа при объявлении переменных. То что возвращаемый тип функции лучше писать именно справа от списка аргументов — достаточно очевидно, но какое это имеет отношение к типу слева или справа при объявлении переменных?


Да, я писал о функциях. Отношение самое прямое — очень удобно, когда типы всех термов (термы — это "имена, обозначающие значения") указываются в одном стиле. Т.е. если у нас тип переменной слева от имени, то и для функции тип (возвращаемый тип) обычно идет слева. Qbit86 даже картинку привел
Автор: Qbit86
Дата: 05.04.21
. Можно, конечно, вразнобой все сделать. Но это будет неестественно:

def magic(Float a, String b, Int c): Float =  a * (b.length() + c)
val (Float, String, Int) => Float magic2 = magic _


Вроде ведь одно и то же объявляется, но какая разница в синтаксисе!

XC>Очевидно что этот синтаксис кривой. Вот вариант на некотором псеводкоде, выглядящий гораздо более понятно и унифицированно:

Не вижу унификации. Почему в определении класса параметры типа идут после имени, а в функции — до? Тогда уж надо

class<A> Test {
  func<B> magic(A a, B b) B {
    return b;
  }
}


XC>Добавление ключевого слова func или fn для функций по аналогии с class и подобными словами (struct, union, enum...) значительно упрощает синтаксис функций. Но является ли такая аналогия полностью верной для переменных (начинать все с ключевых слов — auto, var, val, let...)?


Да! Мне очень нравится такое. Я vim использую, там ключевые слова выделяются. И даже в ide ключевые слова обычно выделяются лучше, чем "C-style" объявление переменной из типа и имени. Плюс все равно есть final/const. Поэтому let/var/val хорошая и очень консистентная замена для "ничего" vs final.
Re: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 21:50
Оценка: 72 (1)
Здравствуйте, x-code, Вы писали:

XC>Современные языки имеют тенденцию ко второму варианту.

XC>Почему? У него есть какое-то фундаментальное преимущество перед первым?

1. Все это полная фигн и вкусовщина. Нет никаких современных и не современных языков. Есть просто выбор авторов. Скажем так C#, Java и С++ дадут фору любому "современному" языку по частоте использования. А 9-й Шарп еще и по фичам.

Постоянно пуши и читаю код на трех языках:
Nemerle - x : int
C#      - int x
C++     - int x

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

Реальные отличия языка в семантике, фичах, рантайме.

2. Как по жизни есть три нотации
int x
x : int
x int

Последняя используется, насколько мне известно, только в Go. Так что серьёзно обсуждать ее распространенность нельзя.

XC>Про т.н. "most vexing parse" я знаю, это ошибка дизайна именно С/С++ и она не имеет прямого отношения к тому что тип идет первым, это скорее проблема того что функция начинается не с ключевого слова (func, fn) а с возвращаемого типа. Сложность с синтаксисом указателей на функции тоже с этим связана.


Как бы отсутствие оной проблемы в C# и Java опровергает твое утверждение про префикс. Это чисто проблема неоднозначности грамматики C.

XC>Все-же интересно, какие преимущества и недостатки есть у обоих способов объявления?


Да никаких. Чистая вкусовщина.

За x : int в Nemerle можно привести в качестве аргумент следующее. В Nemerle конструкция "expression : Type" более универсальна. Ее можно использовать в коде для уточнения типа в любом месте. Скажем вместо такого объявления переменной:
def x : object = expression;

Можно написать:
def x = expression : object;

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

Что касается описания "указателей на функцию" — это, по сути, низкоуровневый бред. В "современных" языках программирования используется концепция функционального типа. В Nemerle (как и во многих функциональных языках) их вообще не нужно объявлять. Они описываются как любой другой тип. Например, функциональный тип из целого в строку:
int -> string

или из двух строк в void
string * string -> void

Скажем если наш метод (функция) хочет принять указатель на функцию функцию преобразующую число в строку, то мы можем описать параметр следующим образом:
public MyFunc(value : int, func : int -> string) : string
{
   "Результат: " + func(value)
}


Если нам понадобится именованный тип, то для этого есть конструкция:
type Псевдоним = Тип;

Скажем, если мы захотим получить тип для функции преобразования числа в строку, то можно написать такой псевдоним:
public type IntToStringType = int -> string;

Теперь можно переписать MyFunc с его помощью:
public MyFunc(value : int, func : IntToStringType) : string
{
   "Результат: " + func(value)
}


Такой подход будет работать и в языках использующих тип спереди (int x):
public string MyFunc(int value, int -> string func)
{
   return "Результат: " + func(value);
}

Как видишь никаких неоднозначностей.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Отредактировано 09.04.2021 21:49 VladD2 . Предыдущая версия . Еще …
Отредактировано 09.04.2021 21:43 VladD2 . Предыдущая версия .
Отредактировано 08.04.2021 22:20 VladD2 . Предыдущая версия .
Re[5]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 21:57
Оценка:
Здравствуйте, x-code, Вы писали:

XC>А если переменная стековая?


Без разницы. Это проблемы восприятия С++-ника. В Шарпе new и структуры объявляет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 22:03
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>По-моему, фундаментальных никаких нету. Просто в теории типов издавна было "x : T", потом так же было в языках ML семейства еще с 70х годов. А теперь авторы языков наконец открыли учебники/статьи и прониклись.


+1

DM>И вообще, почему мы пишем T = v, когда имеем в виду x = v?


Мы пишем:
(x : T) = v

":" — это такой же оператор как и "=", только приоритеты и ассоциативность разные.

DM>И еще лишний символ :, код аж на целую букву длиннее.


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

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

DM>Когда типы слева, делать с функциями хтонический ужас как у Си никто не заставляет, конечно.


+1
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 22:09
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Вот например код вида

XC>
ID1 * ID2;

XC>это что? Это может быть и объявление типа

А зачем для описания указателей использовать тот же оператор, что для умножения?

Это косяк заложенный в язык изначально. В С он обходился тем, что типы заранее известны (предопределены или должны быть заданы тайпдефом). В С++ же это не так. И это создает уже совсем косяк.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 22:11
Оценка:
Здравствуйте, glh, Вы писали:

glh>Больше связано с устройством компилятора и способом помещения объекта в его таблицах/ветвлениях парсера.

glh>"C"/type first-компиляторы более сложные именно в частности потому, что требуют "возврата".

Какого еще возврата? Они требуют связи лексера и типизатора, чтобы определять чем является идентификатор.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 22:13
Оценка: 4 (1)
Здравствуйте, Sharov, Вы писали:

S>Не очень в теме, но какая разница для парсера x int или int x? В каком-то случае правила сложнее?


Рядом же описали. Разница появляется когда добавляется "*". X * Y может быть как объявлением указателя, так и умножением. А компиляторы так устроены, что им нужно это при разборе выражений. С/С++ опираются на знание того чем является X. Если тип, то будет объявление переменной. Если нет, то умножением. Это делает парсер и лексер контекстно-зависимыми, что сильно их усложняет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.21 22:15
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>Если автор языка создает на ровном месте без хороших на то причин суровые неоднозначности в грамматике, а потом героически их преодолевает, то с одной стороны он, конечно, герой, а с другой стороны дурак.


В С-и то неоднозначностей не было. Ричи решил, что раз типы хорошо отличимы, на это можно опираться. Неоднозначность ввел Старуструп, когда решил не отказываться от совместимости с С.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Синтаксис объявления переменных
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 09.04.21 02:13
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А вообще ты странный. Сначала делаешь утвреждение (с которым я согласен), а потом сам начианаешь притягивать за уши что-то чтобы его же и опровергнуть. Ты определить. Или разницы нет. Или она есть.


А ты прочитай целиком сообщение-то. Сначала я говорю, что нет фундаментальных преимуществ. А потом перечисляю недостатки.
Re[4]: Синтаксис объявления переменных
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.04.21 21:51
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>А ты прочитай целиком сообщение-то. Сначала я говорю, что нет фундаментальных преимуществ. А потом перечисляю недостатки.


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