Vantagens de uma sintaxe do idioma da esquerda para a direita

18

Eu assisti a uma entrevista com Herb Sutter no Channel9 e ele mencionou no final do vídeo que a sintaxe da esquerda para a direita estaria no topo de sua lista de desejos para um futuro padrão C ++ (embora ele reconheça a modificação do C ++ dessa maneira seria um animal completamente diferente).

Além de:

  • mais compreensível pelos seres humanos, mais claro a olho nu;

    //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
  • mais fácil de analisar (leva a um melhor suporte à ferramenta, conforme mencionado no vídeo - por exemplo, refatoração de código)

que outras vantagens existem com uma sintaxe "da esquerda para a direita" em uma linguagem de programação? Conheço apenas Pascal e Go empregando esse tipo de sintaxe (e o Go nem percorre todo o caminho, como entendi nesta postagem do blog em que tirei também os exemplos). Seria viável ter uma linguagem de programação de sistemas com esse tipo? de sintaxe?

celavek
fonte
1
haskell usa da esquerda para a direita:f :: (Int -> Int -> Int) -> Int -> Int
Karoly Horvath
1
ActionScript, bem como: function strlen(s:String):int {...}. Além disso, digitou o cálculo lambda (daí, Haskell).
sai em 18/08/11
2
Alguém poderia explicar por favor os votos finais :)? Não vejo motivo para fechá-lo, mas talvez esteja fazendo uma pergunta "errada".
1
Eu não votei para perto, mas, o comentário de @Devjosh é pertinente, é mais apropriado para programadores, espero que alguém irá migrá-lo ....
Nim
3
@ Frank: e não se esqueça, no caso de indicadores de funções, que a sintaxe é estranha, porque o tipo real é dividido ! Isso é um golpe baixo ...
Matthieu M.

Respostas:

12

A vantagem básica é que a análise é mais simples e única. Observe que depois que a linha é analisada, o compilador saberá qual é o tipo exato; portanto, a partir de então, como o tipo foi definido é irrelevante.

Qualquer função que retorne um argumento do tipo de matriz ou tipo de ponteiro de função é difícil de ler atualmente:

// 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] &;

E haveria menos chance de mal-entendidos (como a análise mais irritante ):

// 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{} };

Usando uma abordagem semelhante à inicialização uniforme em C ++ 0x (ou seja, {}para identificar a inicialização). Observe que, com uma abordagem da esquerda para a direita, é muito mais claro o que estamos definindo. Muitas pessoas (eu com certeza) foram mordidas a qualquer momento por esse erro de análise no passado (mais de uma vez), e esse não seria o caso de uma sintaxe da esquerda para a direita.

David Rodríguez - dribeas
fonte
E as expressões? Como essa "sintaxe da esquerda para a direita" afetaria a precedência do operador e a ordem da avaliação?
1
@celavek: Se você voltar à entrevista, notará que ele não deseja alterar toda a sintaxe do idioma, apenas declarações e definições . Obviamente, isso pode permear em outras expressões, não tenho certeza de que a última linha nos exemplos acima seja correta da esquerda para a direita (pense em como o temporário é criado, que talvez precise ser alterado ... em C # que é resolvido por dando dois semântica para o newoperador para structou class, o que não é aplicável a C ++ como no C ++ não há diferenças em tipos de valor de referência /.
David Rodriguez - dribeas
5

Como chegamos aqui

A sintaxe C para declarar pontos de função foi projetada para espelhar o uso. Considere uma declaração de função regular como esta em <math.h>:

double round(double number);

Para ter uma variável de ponto, você pode atribuí-la ao tipo safety usando

fp = round;

você precisaria ter declarado essa fpvariável de ponto desta maneira:

double (*fp)(double number);

Então, tudo o que você precisa fazer é ver como você usaria a função e substituir o nome dessa função por uma referência de ponteiro, transformando-a roundem *fp. No entanto, você precisa de um conjunto extra de parênteses, o que alguns diriam que o torna um pouco mais confuso.

Indiscutivelmente, isso costumava ser mais fácil no C original, que nem tinha assinatura de função, mas não vamos voltar lá, ok?

O lugar em que se torna especialmente desagradável é descobrir como declarar uma função que aceita como argumento ou retorna um ponteiro para uma função, ou ambas.

Se você tivesse uma função:

void myhandler(int signo);

você pode passá-lo para a função de sinal (3) desta maneira:

signal(SIGHUP, myhandler);

ou se você quiser manter o manipulador antigo,

old_handler = signal(SIGHUP, new_handler);

o que é bem fácil. O que é bastante fácil - nem bonito, nem fácil - é acertar as declarações.

signal(int signo, ???)

Bem, você apenas volta à sua declaração de função e troca o nome por uma referência de ponto:

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

Como você não está declarando gotsig, talvez seja mais fácil ler se você omitir:

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

Ou talvez não. :(

Exceto que isso não é bom o suficiente, porque o sinal (3) também retorna o manipulador antigo, como em:

old_handler = signal(SIGHUP, new_handler);

Então agora você precisa descobrir como declarar tudo isso.

void (*old_handler)(int gotsig);

é suficiente para a variável que você vai atribuir. Observe que você realmente não está declarando gotsigaqui apenas old_handler. Então, isso é realmente o suficiente:

void (*old_handler)(int);

Isso nos leva a uma definição correta para o sinal (3):

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

Typedefs para o resgate

A essa altura, acho que todos concordarão que isso é uma bagunça. Às vezes, é melhor nomear suas abstrações; frequentemente, realmente. Com o direito typedef, isso se torna muito mais fácil de entender:

typedef void (*sig_t) (int);

Agora sua própria variável manipuladora se torna

sig_t old_handler, new_handler;

e sua declaração para o sinal (3) se torna apenas

sig_t signal(int signo, sig_t handler);

que de repente é compreensível. Livrar-se do * 's também se livrar de alguns dos parênteses confuso (e eles dizem parens sempre fazer as coisas mais fáceis de entender - hah). Seu uso ainda é o mesmo:

old_handler = signal(SIGHUP, new_handler);

mas agora você tem chance de entender as declarações para old_handler, new_handlere até mesmo signalquando você encontrá-los ou necessidade de escrevê-los.

Conclusão

Acontece que muito poucos programadores C são capazes de elaborar as declarações corretas para essas coisas por conta própria, sem consultar os materiais de referência.

Eu sei, porque uma vez tivemos essa mesma pergunta em nossas perguntas da entrevista para pessoas que trabalham com kernel e driver de dispositivo. :) Certamente, perdemos muitos candidatos dessa maneira quando eles caíram e queimaram no quadro branco. Mas também evitamos contratar pessoas que alegavam ter experiência anterior nessa área, mas que na verdade não podiam fazer o trabalho.

Devido a essa dificuldade generalizada, no entanto, provavelmente não é apenas sensato, mas de fato razoável, ter um caminho a percorrer todas as declarações que não exigem mais que você seja um programador de nerds triplo-alfa, sentado três sigmas acima da média, apenas para usar isso. tipo de coisa confortavelmente.

tchrist
fonte
1
Artificial, difícil de seguir ... +1 para o esforço embora como ele ilustra o ponto que às vezes é difícil conseguir esse direito em C.
celavek
4

Eu acho que você perdeu um pouco o ponto quando se concentrou na parte da esquerda para a direita.

O problema de C e C ++ é a gramática horrenda que eles têm, que é difícil de ler (humanos) e analisar (ferramentas).

Ter uma abordagem mais consistente (ou gramática regular ) facilita as duas coisas. E uma análise mais fácil significa ferramentas mais fáceis: a maioria das ferramentas atuais não acelera o C ++, nem mesmo o mais recente plug-in do Eclipse, pois elas tentaram reinventar a roda ... e falharam, e provavelmente têm mais pessoas do que o projeto médio do sistema operacional.

Então você provavelmente acertou em cheio ao ler e analisar ... e isso é um grande negócio :)

Matthieu M.
fonte
É uma grande surpresa o fato de o Eclipse ainda não conseguir analisar coisas como as declarações acima. Por que eles não usam um analisador de C real como o de gcc?
precisa saber é o seguinte
@ Chris: Eu detectei dois problemas com o Eclipse, e ambos parecem vinculados a macros. Talvez mais um problema de pré-processador do que a geração AST.
Matthieu M.