Преимущества синтаксиса языка слева направо


18

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

Помимо:

  • более понятным для людей, понятнее невооруженным глазом, например

    //C syntax
    
    /*pointer to function taking a pointer to function(which takes 2 integers as 
    
    arguments and returns an int), and an int as arguments and returning an int*/
    
    int (*fp)(int (*ff)(int x, int y), int b)
    
    //Go analogous syntax which is left to write
    
    f func(func(int,int) int, int) int
    
  • легче анализировать (приводит к лучшей поддержке инструмента, как упомянуто в видео - например, рефакторинг кода)

какие еще преимущества есть у синтаксиса «слева направо» в языке программирования. Я знаю только о Pascal и Go, использующих такой синтаксис (и Go даже не идет полным ходом, как я понимаю из этого поста в блоге, из которого я взял также примеры). Было бы возможно иметь язык системного программирования такого рода синтаксиса?


1
Haskell использует слева направо:f :: (Int -> Int -> Int) -> Int -> Int
Кароли Хорват

1
ActionScript , а также: function strlen(s:String):int {...}. Также напечатано лямбда-исчисление (отсюда и Хаскель).
outis

2
Может ли кто-нибудь объяснить, пожалуйста, последние голоса :)? Я не вижу причин для его закрытия, но, возможно, я задаю «неправильный» вопрос.

1
Я не голосовал за закрытие, но комментарий @Devjosh уместен, он больше подходит для программистов, надеюсь, кто-то его перенесет ...
Nim

3
@Frank: и не забывайте, в случае указателей на функции, что синтаксис неудобен, потому что фактический тип разделен ! Это слабый удар ...
Матье М.

Ответы:


12

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

Любую функцию, которая возвращает аргумент типа массива или указатель на функцию, в настоящее время трудно прочитать:

// common way of obtaining the static size in elements of an array:
template <typename T, int N>
char (&array_size_impl( T (&)[N] ))[N];
// alternative parser:
template <typename T, int N>               // this would probably be changed also
array_simple_impl function( T [N] & ) char [N] &;

И было бы меньше шансов на недоразумение (как самый неприятный разбор ):

// Current C++
type x( another_type() );      // create an instance x of type passing a
                               // default constructed another_type temporary?
                               // or declare a function x that returns type and takes as argument
                               // a function that has no arguments and returns another_type
// How the compiler reads it:
x function( function() another_type ) type;

// What many people mean:
x type { another_type{} };

Использование аналогичного подхода для равномерной инициализации в C ++ 0x (т.е. {}для идентификации инициализации). Обратите внимание, что при подходе слева направо гораздо яснее, что мы определяем. Многие люди (я точно) были укушены в любой момент этой ошибкой синтаксического анализа в прошлом (более одного раза), и это не будет иметь место с синтаксисом слева направо.


Как насчет выражений? Как этот «синтаксис слева направо» повлияет на приоритет оператора и порядок вычисления?

1
@celavek: Если вы вернетесь к собеседованию, вы заметите, что он не хочет менять весь синтаксис языка, только декларации s и определения s. Конечно, это может проникнуть в другие выражения, я не слишком уверен, что последняя строка в приведенных выше примерах является правильной слева направо (подумайте о том, как создается временное, что может потребоваться изменить ... в C # это решается путем предоставления newоператорам structили семантике двух операторов семантики class, что не применимо к C ++, поскольку в C ++ нет различий в типах значения / ссылки
David Rodríguez) - dribeas

5

Как мы сюда попали

Синтаксис C для объявления функциональных точек был предназначен для отражения использования. Рассмотрим обычное объявление функции следующим образом <math.h>:

double round(double number);

Чтобы иметь точечную переменную, вы можете присвоить ее типу безопасности, используя

fp = round;

вам нужно было бы объявить эту fpточечную переменную следующим образом:

double (*fp)(double number);

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

Возможно, это было проще в оригинальном C, который даже не имел сигнатуры функции, но давайте не будем возвращаться туда, хорошо?

Место, где это становится особенно неприятным, - выяснить, как объявить функцию, которая либо принимает в качестве аргумента, либо возвращает указатель на функцию, либо и то и другое.

Если у вас была функция:

void myhandler(int signo);

Вы можете передать его функции сигнала (3) следующим образом:

signal(SIGHUP, myhandler);

или если вы хотите сохранить старый обработчик, то

old_handler = signal(SIGHUP, new_handler);

что довольно легко. То, что довольно легко - ни красиво, ни легко - правильно делает декларации.

signal(int signo, ???)

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

signal(int sendsig, void (*hisfunc)(int gotsig));

Поскольку вы не декларируете gotsig, вам может быть легче читать, если вы опустите:

signal(int sendsig, void (*hisfunc)(int));

А может и нет. :(

За исключением того, что это недостаточно хорошо, потому что signal (3) также возвращает старый обработчик, как в:

old_handler = signal(SIGHUP, new_handler);

Так что теперь вы должны выяснить, как объявить все это.

void (*old_handler)(int gotsig);

достаточно для переменной, которую вы собираетесь назначить. Обратите внимание, что вы на самом деле не заявляете gotsigздесь, только old_handler. Так что этого действительно достаточно:

void (*old_handler)(int);

Это приводит нас к правильному определению сигнала (3):

void (*signal(int signo, void (*handler)(int)))(int);

Typedefs на помощь

К этому времени, я думаю, все согласятся, что это беспорядок. Иногда лучше назвать свои абстракции; часто, действительно. При правильном typedefпонимании это становится намного проще:

typedef void (*sig_t) (int);

Теперь ваша собственная переменная обработчика становится

sig_t old_handler, new_handler;

и ваша декларация для сигнала (3) становится просто

sig_t signal(int signo, sig_t handler);

что вдруг понятно. Избавление от * также избавляет от некоторых запутанных скобок (и они говорят, что парены всегда облегчают понимание - ха!). Ваше использование остается прежним:

old_handler = signal(SIGHUP, new_handler);

но теперь у вас есть шанс понять заявления для old_handler, new_handlerи даже signalкогда вы впервые сталкиваетесь с ними или должны их написать.

Вывод

Оказывается, очень немногие программисты на Си способны самостоятельно разрабатывать правильные декларации для этих вещей, не обращаясь к справочным материалам.

Я знаю, потому что у нас когда-то был этот вопрос на наших собеседованиях для людей, выполняющих работу с ядром и драйвером устройства. :) Конечно, мы потеряли много кандидатов, потому что они разбились и сгорели на доске. Но мы также избегали нанимать людей, которые утверждали, что имели предыдущий опыт работы в этой области, но на самом деле не могли выполнить работу.

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


1
Придумано, трудно следовать ... +1 за усилие, хотя оно иллюстрирует тот факт, что иногда трудно понять это правильно в C.
celavek

4

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

Проблема C и C ++ заключается в ужасающей грамматике, которую они имеют, которую трудно читать (люди) и анализировать (инструменты).

Наличие более последовательной (или регулярной ) грамматики облегчает обе эти задачи. А более легкий анализ означает более простой инструмент: большинство современных инструментов не понимают C ++ правильно, даже самый последний плагин Eclipse, поскольку они стремились изобрести колесо ... и потерпели неудачу, и у них, вероятно, больше людей, чем в среднем проекте ОС.

Таким образом, вы, вероятно, прибили это, сосредоточившись на чтении и разборе ... и это большое дело :)


Это большой сюрприз, что Eclipse все еще не может разобрать такие вещи, как заявления выше. Почему они не используют настоящий синтаксический анализатор C, как из gcc?
tchrist

@tchrist: я обнаружил две проблемы с Eclipse, и обе, похоже, связаны с макросами. Возможно, это скорее проблема препроцессора, чем генерация AST.
Матье М.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.