Como verificar / provar a ortogonalidade de uma linguagem de programação?

10

Conheço o conceito de ortogonalidade, mas, do ponto de vista da linguagem de programação, existe uma maneira de verificar / provar?

Por exemplo, em C #, pode-se usar publicou staticpara uma assinatura de método. Você pode usar um ou ambos e eles não interferem um com o outro, então eles são ortogonais entre si, certo?

Minha pergunta é: como eu ligo para o restante dos recursos, principalmente os que não estão relacionados entre si?

Todos os recursos precisam coexistir / empilhar juntos?

Existe uma linguagem de programação 100% ortogonal?

Joan Venge
fonte
Comece do (próximo) começo: linguagem assembly?
Matthew Flynn
11
Para realmente provar algo, você precisa de uma definição formal. E se a sua definição for algo tão grande quanto a especificação C #, provar que algo exigirá muito trabalho.
precisa

Respostas:

4

Não tenho certeza de que a ortogonalidade possa servir como métrica útil ou válida em caso de linguagens de ordem geral de uso geral como C #, porque exige a distinção de "operações" e "operandos" - as pequenas partes da linguagem que não são facilmente distinguível em idiomas de alta ordem como C #.

Meu entendimento da ortogonalidade é baseado na linguagem Assembler, em que a ortogonalidade do conjunto de instruções de uma determinada CPU ou microcontrolador indicava se existem algumas restrições nas operações executadas por essa CPU ou controlador, dependendo dos tipos de dados. Nos primeiros tempos, isso era importante, porque nem toda CPU suportava operações em números fracionários ou de tamanho diferente, etc.

A esse respeito, eu preferiria verificar a ortogonalidade da Common Intermediate Language usando a linguagem Stack Machine como o destino do compilador C #, não o próprio C #.

Se você está realmente interessado na ortogonalidade do C # e não me engano aqui (para qualquer finalidade), sugiro procurar alguns algoritmos de programação genética . Você pode usá-los para gerar programas diferentes do conjunto de palavras-chave fornecido (mesmo os sem sentido) e apenas verificar automaticamente se são compiláveis. Isso ajudaria você a ver automaticamente quais elementos da linguagem podem ser combinados e derivar alguns aspectos da sua métrica de ortogonalidade.

Alexander Galkin
fonte
6

O termo "ortogonalidade" é um termo leigo para uma noção matemática precisa: os termos da linguagem formam uma álgebra inicial (consulte na Wikipedia).

Basicamente, significa "existe uma correspondência 1-1 entre sintaxe e significado". Isso significa: existe exatamente uma maneira de expressar as coisas e, se você pode colocar alguma expressão em um determinado local, também pode colocar qualquer outra expressão lá.

Outra maneira de pensar em "ortogonal" é que a sintaxe obedece ao princípio da substituição. Por exemplo, se você tiver uma instrução com um slot para uma expressão, qualquer expressão poderá ser colocada lá e o resultado ainda será um programa sintaticamente válido. Além disso, se você substituir

Quero enfatizar que "significado" não implica resultado computacional. Claramente, 1 + 2 e 2 + 1 são iguais a 3. No entanto, os termos são distintos e implicam um cálculo diferente, mesmo que tenha o mesmo resultado. O significado é diferente, assim como dois algoritmos de classificação são diferentes.

Você pode ter ouvido falar em "árvore de sintaxe abstrata" (AST). A palavra "abstrato" aqui significa precisamente "ortogonal". Tecnicamente, a maioria dos AST não é de fato abstrata!

Talvez você já tenha ouvido falar da linguagem de programação "C"? A notação de tipo C não é abstrata. Considerar:

int f(int);

Então, aqui está uma declaração de função retornando o tipo int. O tipo de um ponteiro para esta função é dado por:

int (*)(int)

Note que você não pode escrever o tipo da função! A notação do tipo C é péssima! Não é abstrato. Não é ortogonal. Agora, suponha que desejemos criar uma função que aceite o tipo acima, em vez de int:

int (*) ( int (*)(int) )

Tudo bem .. mas .. e se quisermos devolvê-lo:

int (*)(int) (*) (int)

Woops! Inválido. Vamos adicionar parênteses:

(int (*)(int)) (*) (int)

Woops! Isso também não funciona. Temos que fazer isso (é a única maneira!):

typedef int (intintfunc*) (int);
intintfunc (*)(int)

Agora está tudo bem, mas ter que usar um typedef aqui é ruim. C é péssimo. Não é abstrato. Não é ortogonal. Veja como você faz isso no ML, que é:

 int -> (int -> int)

Condenamos C no nível da sintaxe.

Ok, agora vamos flogar C ++. Podemos corrigir a estupidez acima com modelos e obter uma notação de tipo ML (mais ou menos):

fun<int, int>
fun< fun<int,int>, int>

mas o sistema de tipos real é fundamentalmente falho por referências: se Té um tipo, então é T&um tipo? A resposta é waffly: no nível da sintaxe, se você possui um tipo U = T &, então U & é permitido, mas significa apenas T &: uma referência a uma referência é a referência original. Isso é péssimo! Ele quebra o requisito de exclusividade semanticamente. Pior: T& & não é permitido sintaticamente: isso quebra o princípio da substituição. Portanto, as referências C ++ quebram a ortogonalidade de duas maneiras diferentes, dependendo do tempo de ligação (análise ou análise de tipo). Se você quer entender como fazer isso direito .. não há problema com ponteiros!

Quase nenhuma linguagem real é ortogonal. Mesmo Scheme, que finge grande clareza de expressão, não é. No entanto, muitas linguagens boas podem ser consideradas como tendo "uma base razoavelmente próxima da característica ortogonal" e essa é uma boa recomendação para uma linguagem, aplicada tanto à sintaxe quanto à semântica subjacente.

Yttrill
fonte
Então você acha que ML é mais ortogonal que outros? E Lisp e Haskell?
Joan Venge
11
@joan: bem, lisp não tem nenhum recursos para que ele satisfaz a exigência em vaccuuo :)
Yttrill
@joan: Eu não sou um programador Haskell, então é um pouco difícil dizer, mas a presença no Haskell de "funcionalidade de nível extremamente alto" é indicativa de forte ortogonalidade: você simplesmente não pode ter uma implementação coerente de mônadas ou setas, a menos que o resto da língua tem "ortogonalidade" substancial
Yttrill
O que você pensa sobre Pascal. Parece que as cargas são melhores que c. #
306
Eu sei que meu comentário está quase 4 anos atrasado, mas eu me deparei com ele. Esta resposta está errada em quase tudo. Até o todo "é o único caminho!" parte está simplesmente errada. Você pode expressar facilmente que, sem um exemplo de typedef int (*intintfunc())(int) { ... }- intintfunc é uma função que não aceita argumentos e retorna um ponteiro para uma função que aceita 1 argumento int e retorna um valor int.
Wiz
4

Provar ortogonalidade é negativo. Isso significa que você não tem nenhuma construção que não seja ortogonal, o que significa que é muito mais fácil provar que algo não é ortogonal.

Na prática, a maioria das pessoas fala sobre a ortogonalidade das linguagens de programação em termos de graus, em vez de serem completamente ortogonais ou não. Quando o conhecimento de fazer algo em um contexto se traduz em outro contexto e "faz o que você espera", essa linguagem é considerada mais ortogonal. O LISP é considerado altamente ortogonal porque tudo é uma lista, mas não acho que se possa dizer que seja 100% ortogonal devido a algumas redundâncias que facilitam o uso. O C ++ é considerado não muito ortogonal, porque existem muitas "dicas" em que não funciona exatamente como você pensa.

Karl Bielefeldt
fonte
3

Aviso, não sei nada sobre esse tópico.

Uma rápida olhada na Wikipedia parece indicar que a ortogonalidade é principalmente direcionada a padrões de design e sistemas. Em termos de linguagens de programação, a entrada indica que os conjuntos de instruções são ortogonais se houver uma e apenas uma instrução para cada ação possível, ou melhor, nenhuma instrução se sobrepõe a outra.

Para C #, eu imagino que seja ortogonal, pois a maioria dos truques de sintaxe ( foreachvem à mente) são simplesmente front-ends para versões especialmente formadas da construção base ( foreachse transforma em forloops). No geral, o idioma apenas suporta realmente fazer as coisas de uma única maneira, mesmo que o açúcar sintático forneça maneiras adicionais de fazê-las. E, finalmente, tudo se compila até MSIL(ou como é chamado hoje em dia) e MSILprovavelmente é ortogonal.

Se você fizer uma ressalva de que o material sintático de açúcar é essencialmente um "invólucro" para fazê-lo da "maneira mais difícil", você pode analisar os vários recursos da linguagem, omitindo o açúcar, e ver se há alguma construção que realmente se sobreponha. Caso contrário, imagino que você possa declarar a linguagem ortogonal.

Meus dois centavos.

digitlworld
fonte
Eu acho que se for e foreach são características de uma linguagem, enquanto uma é um açúcar sintático da outra (onde os efeitos da foreach poderiam ser alcançados usando for), a linguagem perde sua ortogonalidade por lá.
precisa saber é o seguinte
Não do...whilepode ser usado para fornecer o mesmo efeito que for? Eu nunca ouvi falar disso como sendo considerado açúcar sintático.
Matthew Flynn
11
@MatthewFlynn: Bah! Eles são AMBOS açúcar sintático, você pode substituir sua iteração por uma função recursiva! ;)
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: Isso não é apenas açúcar sintático para os GOTO?
Ivan Ivan
2
O @MatthewFlynn do whilegarante uma execução de loop único e verifica a condição após o fato. forverifica a condição primeiro e não garante uma única execução.
digitlworld
2

Minha pergunta é: como eu ligo para o restante dos recursos, principalmente os que não estão relacionados entre si?

Você continua fazendo o que está fazendo, enumerando todas as combinações que funcionam ou são proibidas.

Isso é tudo. É muito doloroso de fazer.

Todos os recursos precisam coexistir / empilhar juntos?

Se todos os recursos puderem ser particionados em subconjuntos disjuntos que não interfiram entre si, é claro que tudo seria sensato.

Todas as estruturas de dados funcionam com todos os tipos primitivos. Todos os operadores de expressão trabalham com todos os tipos. Essas são definições comuns de ortogonalidade. Mas você pode querer mais (ou menos)

Às vezes, no entanto, há casos especiais devido a sistemas operacionais ou bibliotecas herdadas que não são ortogonais.

Além disso, alguns tipos não são realmente muito conformáveis. Por exemplo, o Python permite comparar dois objetos de dicionário para "fazer pedidos". Mas quase não existe uma definição sensata de "ordenação" entre os dicionários. Python define um, mas é bastante discutível. Esse caso especial faz com que os dicionários falhem em um teste de ortogonalidade?

Quão ortogonal é "ortogonal o suficiente"? O que você precisa ver para se sentir feliz com o grau de ortogonalidade no seu idioma.

S.Lott
fonte
2

A lista de recursos não-ortogonais é realmente longa na maioria das linguagens de programação, por exemplo

  • classes anônimas conflitam com a reflexão do java
  • conflito de apagamento genérico e de tipo com reflexão de java
  • matrizes são um pouco diferentes de outros objetos, devido ao seu tipo especial, mesmo que sejam objetos.
  • métodos estáticos vs. instância não são os mesmos, por exemplo, você não pode substituir um método estático
  • classe aninhada é uma reflexão tardia
  • impacto da digitação dinâmica versus estática na estratégia de envio de mensagens (veja, por exemplo, este caso extremo em C #)
  • etc.

Esses são apenas alguns que me vêm à mente, mas existem muitos outros, e também em outros idiomas.

É difícil garantir que não haja interferência sutil entre os recursos do idioma. Como CAR Hoare indica em seu artigo "Hints on Programming Language Design":

Parte do design da linguagem consiste em inovação. Esta atividade leva a novos recursos de idioma isoladamente. A parte mais difícil do design de linguagem está na integração : selecionar um conjunto limitado de recursos de idioma e aperfeiçoá-los até o resultado ser uma estrutura simples e consistente que não tenha mais arestas.

Provavelmente, uma boa jogada para aumentar a ortogonalidade é unificar conceitos (que vai na direção da resposta de @ karl-bielfeld). Se tudo estiver, digamos, uma lista ou um objeto, as chances são de que haverá menos conflitos. Ou, em vez de aninhar a classe após uma reflexão, torne-a um dos principais recursos.

A maioria dos documentos sobre linguagens de programação prova certas propriedades da linguagem (por exemplo, solidez do tipo) em um subconjunto (um "núcleo") da linguagem formalizada. Aqui devemos fazer o oposto, provar que todos os recursos são compostos com segurança. Além disso, isso significa que se deve definir o que significa "compor". Isso significa "correr"? (Nesse caso, o link acima sobre o case de borda com digitação estática e dinâmica é seguro). Isso significa ser "seguro"? Isso significa ser previsível do ponto de vista do desenvolvedor?

Tudo isso é muito interessante - mas também muito desafiador.

ewernli
fonte