Come verificare / dimostrare l'ortogonalità di un linguaggio di programmazione?


10

Conosco il concetto di ortogonalità, ma dal punto di vista del linguaggio di programmazione esiste un modo per verificarlo / dimostrarlo?

Ad esempio in C #, si può usare publico staticper una firma del metodo. Puoi usare uno o entrambi e non interferirebbero tra loro, quindi sono ortogonali tra loro, giusto?

La mia domanda è: come posso fare per il resto delle funzionalità, in particolare quelle che non sono correlate tra loro?

Tutte le funzionalità devono coesistere / impilare insieme?

Esiste un linguaggio di programmazione ortogonale al 100%?


Inizia dall'inizio (vicino): linguaggio assembly?
Matthew Flynn,

1
Per dimostrare effettivamente qualcosa, è necessaria una definizione formale per questo. E se la tua definizione sarà grande quanto la specifica C #, provare qualsiasi cosa richiederà molto lavoro.
svick

Risposte:


4

Non sono sicuro che l'ortogonalità possa servire come metrica utile o valida in caso di linguaggi di ordine generale di uso generale come C #, perché richiede la distinzione di "operazioni" e "operandi" - le piccole parti del linguaggio che non sono facilmente distinguibile in linguaggi di così alto ordine come C #.

La mia comprensione dell'ortogonalità si basa sul linguaggio Assembler in cui l' ortogonalità dell'insieme di istruzioni di una determinata CPU o microcontrollore specifica ha indicato se vi sono alcuni vincoli sulle operazioni eseguite da questa CPU o controller a seconda dei tipi di dati. All'inizio questo era importante perché non tutte le CPU supportavano operazioni su numeri frazionari o numeri di diversa lunghezza ecc.

A questo proposito, preferirei verificare l'ortogonalità del Common Intermediate Language usando il linguaggio Stack Machine come target per il compilatore C #, non C # stesso.

Se sei veramente interessato all'ortogonalità di C # e non mi sbaglio qui (per scopi particolari), suggerirei di cercare alcuni algoritmi di programmazione genetica . Puoi usarli per generare programmi diversi dal set di parole chiave specificato (anche quelli senza significato) e puoi semplicemente verificare automaticamente se sono compilabili. Ciò ti aiuterebbe a vedere automaticamente quali elementi del linguaggio possono essere combinati insieme e derivare alcuni aspetti della tua metrica di ortogonalità.


6

Il termine "ortogonalità" è il termine di un laico per una nozione matematica precisa: i termini del linguaggio formano una algebra iniziale (cercalo su Wikipedia).

Significa sostanzialmente "esiste una corrispondenza 1-1 tra sintassi e significato". Ciò significa: esiste esattamente un modo per esprimere le cose e, se riesci a mettere qualche espressione in un determinato luogo, allora puoi inserire anche qualsiasi altra espressione lì.

Un altro modo di pensare a "ortogonale" è che la sintassi obbedisce al principio di sostituzione. Ad esempio, se si dispone di un'istruzione con uno slot per un'espressione, allora è possibile inserire qualsiasi espressione e il risultato è ancora un programma sintatticamente valido. Inoltre, se si sostituisce

Voglio sottolineare che il "significato" non implica un risultato computazionale. Chiaramente, 1 + 2 e 2 + 1 sono entrambi uguali 3. Tuttavia i termini sono distinti e implicano un calcolo diverso anche se ha lo stesso risultato. Il significato è diverso, così come due algoritmi di ordinamento sono diversi.

Potresti aver sentito parlare di "albero di sintassi astratto" (AST). La parola "astratto" qui significa precisamente "ortogonale". Tecnicamente la maggior parte degli AST non sono in realtà astratti!

Forse hai sentito parlare del linguaggio di programmazione "C"? La notazione di tipo C non è astratta. Prendere in considerazione:

int f(int);

Quindi ecco una dichiarazione di funzione che restituisce il tipo int. Il tipo di puntatore a questa funzione è dato da:

int (*)(int)

Nota, non puoi scrivere il tipo di funzione! La notazione di tipo C fa schifo! Non è astratto. Non è ortogonale. Supponiamo ora di voler creare una funzione che accetti il ​​tipo sopra invece di int:

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

Tutto ok .. ma .. cosa succede se vogliamo invece restituirlo:

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

Woops! Non valido. Consente di aggiungere parentesi:

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

Woops! Neanche questo funziona. Dobbiamo farlo (è l'unico modo!):

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

Ora va bene, ma dover usare un typedef qui è male. C fa schifo. Non è astratto. Non è ortogonale. Ecco come farlo in ML, che è:

 int -> (int -> int)

Condanniamo C a livello di sintassi.

Ok, ora lascia flog C ++. Possiamo correggere la stupidità sopra con i modelli e ottenere una ML come notazione (più o meno):

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

ma l'attuale sistema di tipi è fondamentalmente imperfetto dai riferimenti: se Tè un tipo, allora è T&un tipo? La risposta è waffly: a livello di sintassi, se hai un tipo U = T &, allora U & è permesso ma significa solo T &: un riferimento a un riferimento è il riferimento originale. Questo fa schifo! Rompe semanticamente il requisito di unicità. Peggio ancora: T & & non è permesso sintatticamente: questo rompe il principio di sostituzione. Quindi i riferimenti C ++ interrompono l'ortogonalità in due modi diversi, a seconda del tempo di associazione (analisi o analisi del tipo). Se vuoi capire come farlo bene ... non c'è problema con i puntatori!

Quasi nessuna lingua reale è ortogonale. Perfino Scheme, che pretende una grande chiarezza espressiva, non lo è. Tuttavia, molte buone lingue possono essere giudicate avere una "ragionevolmente vicina alla base delle caratteristiche ortogonali" e questa è una buona raccomandazione per una lingua, applicata sia alla sintassi che alla semantica sottostante.


Quindi pensi che ML sia più ortogonale di altri? Che dire di Lisp e Haskell?
Joan Venge,

1
@joan: beh, lisp non ha nessuna caratteristica quindi soddisfa i requisiti di vaccuuo :)
Yttrill

@joan: Non sono un programmatore di Haskell, quindi è un po 'difficile da dire, ma la presenza in Haskell di "funzionalità di altissimo livello" è indicativa di una forte ortogonalità: semplicemente non puoi avere un'implementazione coerente di Monads o Frecce a meno che il resto della lingua ha una sostanziale "ortogonalità"
Yttrill

Cosa ne pensi di Pascal. Sembra molto meglio di C.
supercat

So che il mio commento è in ritardo di quasi 4 anni, ma mi sono appena imbattuto. Questa risposta è sbagliata su quasi tutto. Anche l'intero "è l'unico modo!" la parte è semplicemente sbagliata. Puoi facilmente esprimerlo senza un esempio di typedef int (*intintfunc())(int) { ... }: intintfunc è una funzione che non accetta argomenti e restituisce un puntatore a una funzione che accetta 1 argomento int e restituisce un valore int.
Wiz,

4

Dimostrare che l'ortogonalità si sta rivelando negativa. Significa che non hai costrutti che non sono ortogonali, il che significa che è molto più facile provare che qualcosa non è ortogonale di quello che è.

In pratica, molte persone parlano dell'ortogonalità dei linguaggi di programmazione in termini di gradi piuttosto che essere completamente ortogonali o meno. Quando la conoscenza del fare qualcosa in un contesto si traduce in un altro contesto e "fa quello che ti aspetti", si dice che quella lingua è più ortogonale. LISP è considerato altamente ortogonale perché tutto è un elenco, ma non credo si possa dire che sia ortogonale al 100% a causa di alcuni esuberi che ne facilitano l'uso. Il C ++ è considerato non molto ortogonale perché ci sono molti piccoli "trucchi" in cui non funziona esattamente come pensi.


3

Attenzione, non so nulla di questo argomento.

Una rapida occhiata a Wikipedia sembra indicare che l'ortogonalità è principalmente orientata ai modelli di progettazione e alla progettazione di sistemi. In termini di linguaggi di programmazione, la voce indica che i set di istruzioni sono ortogonali se esiste una e una sola istruzione per ogni azione possibile, o meglio, nessuna istruzione si sovrappone a un'altra.

Per C #, immagino che sia ortogonale, in quanto la maggior parte dei trucchi di sintassi (mi foreachviene in mente) sono semplicemente front-end per versioni appositamente formate del costrutto di base ( foreachdiventano forloop). Nel complesso, la lingua supporta veramente solo il fare le cose in un solo modo, anche se lo zucchero sintattico fornisce ulteriori modi per farle. E infine, si compila fino a MSIL(o come si chiama in questi giorni) ed MSILè probabilmente ortogonale.

Se si avverte che la roba sintetica dello zucchero è essenzialmente un "involucro" attorno a farlo nel "modo difficile", è possibile analizzare le varie caratteristiche del linguaggio, omettendo lo zucchero e vedere se ci sono costrutti che si sovrappongono davvero. Altrimenti, immagino che potresti dichiarare la lingua ortogonale.

I miei due centesimi.


Penso che se sia a favore che a foreach sono caratteristiche di una lingua mentre una è uno zucchero sintattico dell'altra (dove gli effetti della foreach potrebbero essere raggiunti usando for), la lingua perde lì la sua ortogonalità.
vpit3833,

Non do...whilepuò essere usato per fornire lo stesso effetto di for? Non ho mai sentito parlare di nessuno come considerato zucchero sintattico.
Matthew Flynn,

1
@MatthewFlynn: Bah! Sono ENTRAMBE lo zucchero sintattico, potresti semplicemente sostituire la tua iterazione con una funzione ricorsiva! ;)
FrustratedWithFormsDesigner

2
@FrustratedWithFormsDesigner: Non è solo zucchero sintattico per GOTO?
Ivan

2
@MatthewFlynn do whilegarantisce l'esecuzione di un singolo ciclo e verifica la condizione dopo il fatto. forcontrolla prima la condizione e non garantisce una singola esecuzione.
digitlworld,

2

La mia domanda è: come posso fare per il resto delle funzionalità, in particolare quelle che non sono correlate tra loro?

Continui a fare quello che stai facendo, elencando tutte le combinazioni che funzionano o sono vietate.

È tutto. È abbastanza doloroso da fare.

Tutte le funzionalità devono coesistere / impilare insieme?

Se tutte le funzionalità possono essere suddivise in sottoinsiemi disgiunti che non interferiscono l'uno con l'altro, allora tutto sarebbe sensato.

Tutte le strutture di dati funzionano con tutti i tipi primitivi. Tutti gli operatori di espressioni lavorano con tutti i tipi. Queste sono definizioni comuni di ortogonalità. Ma potresti volere di più (o meno)

A volte, tuttavia, ci sono casi speciali a causa di sistemi operativi o librerie legacy che non sono ortogonali.

Inoltre, alcuni tipi non sono affatto molto conformi. Ad esempio Python consente di confrontare due oggetti del dizionario per "ordinare". Ma non esiste quasi una definizione ragionevole di "ordinamento" tra i dizionari. Python ne definisce uno, ma è piuttosto discutibile. Questo caso speciale fa sì che i dizionari non superino un test di ortogonalità?

Quanto è ortogonale "abbastanza ortogonale"? Cosa devi vedere per essere felice con il grado di ortogonalità nella tua lingua.


2

L'elenco delle funzionalità non ortogonali è davvero lungo nella maggior parte dei linguaggi di programmazione, ad es

  • classi anonime sono in conflitto con la riflessione java
  • conflitto di cancellazione generica e di tipo con java reflection
  • le matrici sono in qualche modo diverse dagli altri oggetti, a causa del loro tipo speciale, anche se sono oggetti.
  • i metodi statici vs. istanza non sono gli stessi, ad esempio non è possibile ignorare un metodo statico
  • la classe nidificata è un ripensamento
  • impatto della tipizzazione dinamica vs. statica sulla strategia di invio dei messaggi (vedi ad esempio questo caso limite in C #)
  • eccetera.

Quei pochi che mi vengono in mente, ma ce ne sono molti altri, e anche in altre lingue.

È difficile garantire che non vi siano sottili interferenze tra le funzionalità del linguaggio. Come CAR Hoare indica nel suo documento "Suggerimenti per la progettazione del linguaggio di programmazione":

Parte del design del linguaggio consiste nell'innovazione. Questa attività porta a nuove funzionalità linguistiche in isolamento. La parte più difficile della progettazione del linguaggio risiede nell'integrazione : selezionare un insieme limitato di funzionalità del linguaggio e lucidarle fino a quando il risultato è una struttura semplice coerente che non ha più bordi irregolari.

Probabilmente, una buona mossa per aumentare l'ortogonalità è l'unificazione dei concetti (che va nella direzione della risposta @ karl-bielfeld). Se tutto è, diciamo un elenco o un oggetto, è probabile che ci saranno meno conflitti. O invece di avere una classe nidificata dopo un pensiero, rendilo una funzionalità di base.

La maggior parte degli articoli sui linguaggi di programmazione dimostra alcune proprietà del linguaggio (ad esempio, la solidità del tipo) su un sottoinsieme (un "nucleo") del linguaggio che è formalizzato. Qui dovremmo fare il contrario, dimostrare che tutte le funzionalità si compongono in modo sicuro. Inoltre, ciò significa che si dovrebbe definire cosa significa "comporre". Significa "correre"? (In questo caso, il collegamento sopra relativo alla custodia perimetrale con digitazione dinamica e statica è sicuro). Vuol dire essere "sicuri"? Significa essere prevedibili dal punto di vista degli sviluppatori?

Tutto ciò è molto interessante, ma anche molto stimolante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.