Perché alcuni linguaggi di programmazione di Turing sono completi ma mancano alcune abilità di altri linguaggi?


42

Mi sono imbattuto in uno strano problema durante la scrittura di un interprete che (dovrebbe) agganciarsi a programmi / funzioni esterni: le funzioni in 'C' e 'C ++' non possono agganciare funzioni variadiche , ad esempio non riesco a creare una funzione che chiama 'printf' con gli stessi argomenti esatti che ha ottenuto, e invece deve chiamare una versione alternativa che accetta un oggetto variadic. Questo è molto problematico poiché voglio essere in grado di creare un oggetto con un gancio anonimo.

Quindi, ho pensato che fosse strano dal momento che Forth , JavaScript e forse una pletora di altre lingue possono farlo molto facilmente senza dover ricorrere al linguaggio assembly / codice macchina. Dato che altre lingue possono farlo così facilmente, ciò significa che la classe di problemi che ciascun linguaggio di programmazione può risolvere in realtà varia in base alla lingua, anche se queste lingue sono tutte complete di Turing ?


33
Dovresti esaminare i tarping turing . Sono un affascinante tipo di linguaggio di programmazione che ha intenzionalmente il minor numero di operazioni disponibili mentre è ancora completo. Molti di loro mancano di tipi di dati di base, funzioni e persino cose semplici come la dichiarazione di variabili. Personalmente, penso che codificarli sia molto divertente, dal momento che ti costringe a uscire dalla tua zona di comfort per provare qualcosa di inutilmente difficile.
DJMcMayhem,

8
Guarda cosa fa una macchina Turing e vedrai che "Turing-complete" è un ostacolo molto basso da eliminare. Fondamentalmente tutto ciò che può 'leggere', 'scrivere' e 'saltare' mentre supporta l'aritmetica di base è Turing completo. Questo è ben al di sotto del livello delle funzionalità linguistiche che stai guardando.
aroth

2
Per essere ancora più ridicoli: l'istruzione MOV su un processore x86 è Turing completa. Vedi cl.cam.ac.uk/~sd601/papers/mov.pdf per i dettagli.
Gareth McCaughan,

3
Anche il gioco di carte Magic: The Gathering è Turing completo. La completezza di Turing non ha quasi nulla a che fare con le caratteristiche di una lingua.
Albero

2
A proposito ci sono anche linguaggi di programmazione molto complicati che non sono completi, come quei correttori di bozze.
盛安安

Risposte:


68

Le lingue complete di Turing possono calcolare lo stesso insieme di funzioni , che è l'insieme delle funzioni parziali ricorsive generali. Questo è tutto.NkN

Questo non dice nulla sulle caratteristiche del linguaggio. Una macchina di Turing ha caratteristiche compositive molto limitate. Il -calculus non tipizzato è molto più compositivo, ma manca di molte caratteristiche che si trovano comunemente nelle lingue moderne.λ

La completezza di Turing non dice nulla sull'avere tipi, array incorporati / numeri interi / dizionari, capacità di input / output, accesso alla rete, multithreading, allocazione dinamica, ...

Solo perché Java non ha la funzione X (ad esempio macro, tipi di livello superiore o tipi dipendenti), non smette improvvisamente di essere Turing completo.

La completezza e l'espressività del linguaggio sono due nozioni diverse.


42
Edwin Brady, il progettista di Idris, (solo la metà) usa scherzosamente (non so se l'ha inventato) il termine "Tetris-completo" per esprimere questa differenza tra "può calcolare qualsiasi funzione calcolabile su numeri naturali" e "può essere utilizzato per scrivere programmi non banali che interagiscono con l'ambiente ".
Jörg W Mittag,

12
Potresti anche menzionare che potresti avere queste cose in C / C ++. Dovresti solo scrivere del codice che funge da compilatore in C / C ++ per un'altra lingua che li ha dove il tuo codice compila una stringa nel tuo codice C / C ++. Quindi puoi semplicemente programmare in qualcosa come Java nel tuo file C. Tutto questo richiederebbe molto lavoro, ma è possibile (chiaramente perché C / C ++ è Turing Complete).
Shufflepants,

4
@Shufflepants Mi chiedo comunque se sia davvero utile dire "Posso farlo in C ++ dal momento che posso interpretare un'altra lingua". Con ciò, Java, ML, C ++ sono equivalenti. Anche le TM con un oracolo per syscalls (I / O) sono equivalenti. Temo che, con questo ragionamento, praticamente tutte le lingue siano ugualmente espressive. In tal caso, non è una nozione utile confrontare le lingue.
Chi,

9
@chi Hai ragione, sono equivalenti. Questo è ciò che significa essere Turing Complete. Tutto ciò che può essere fatto con un sistema Turing Complete può essere fatto in un altro sistema Turing Complete. Potrebbe non essere conveniente farlo in un particolare sistema. E la comodità di fare varie cose è il modo principale per confrontare diversi linguaggi di programmazione. Ma non è questa la domanda.
Shufflepants,

5
@Rhymoid Se C non è Turing completo per motivi di accesso alla memoria o perché non è possibile essere in grado di inviare segnali arbitrari a un dispositivo connesso che ha una memoria arbitrariamente grande, allora si potrebbe sostenere che nessun linguaggio o dispositivo del mondo reale è Turing completo . Ma non credo sia una linea di ragionamento molto produttiva.
Shufflepants,

47

La completezza di Turing è un concetto astratto di calcolabilità. Se una lingua è Turing completa, allora è in grado di fare qualsiasi calcolo che può fare qualsiasi altra lingua completa di Turing.

Questo, tuttavia, non dice quanto sia conveniente farlo. Alcune funzioni semplici in alcune lingue possono essere molto difficili in altre, a causa delle scelte progettuali. La completezza di Turing dice solo che puoi fare il calcolo. A titolo di esempio estremo, può essere difficile agganciare funzioni varadiche in C ++, ma è possibile scrivere un interprete JavaScript in C ++ che può agganciare funzioni variadiche.

Il design del linguaggio è un'arte piuttosto interessante. Uno dei passi principali che uno deve intraprendere è identificare quali comportamenti vuoi formare la spina dorsale della tua lingua. Questi comportamenti sono cose che sono facili da fare nella tua lingua perché sono integrati nel piano terra. Prendiamo decisioni di progettazione su quali funzioni includere in ogni lingua.

Per quanto riguarda il tuo esempio particolare, quando C è stato sviluppato, è stato progettato per funzionare molto vicino al modo in cui operavano le lingue di assemblaggio del giorno. Le funzioni variabili hanno semplicemente spinto gli argomenti sullo stack, con pochissima sicurezza tipografica. L'implementazione di queste funzioni variadiche è stata lasciata al compilatore, per garantire la massima portabilità. Di conseguenza, sono state formulate pochissime ipotesi sulle capacità dell'hardware. Quando arrivò JavaScript, la scena era cambiata. Funziona già in una macchina virtuale come linguaggio interpretato, quindi l'equilibrio si sposta verso la convenienza. Consentire l'aggancio delle funzioni variadiche diventa ragionevole. Anche nel caso di JavaScript compilato Just In Time, i nostri compilatori sono disposti a memorizzare molte più informazioni aggiuntive sugli argomenti rispetto a quelle che i compilatori C di un tempo erano disposti a memorizzare.


1
@Wildcard Considererei (virtualmente) tutti i compilatori come sbagliati. La maggior parte delle lingue sono complete di Turing, ma alla fine devono essere interpretate / compilate in assembly, il che non lo è. Ma questa è una limitazione fisica necessaria: l'hardware non è mai potente. Tuttavia, la calcolabilità offre molti concetti utili che "si applicano", in senso pratico, ai computer del mondo reale.
chi,

3
@Wildcard In quel senso banale. Assembly (e C) ha puntatori di dimensioni fisse, che possono indirizzare solo una quantità limitata di memoria. Potremmo, teoricamente parlando, definire un assembly in cui i puntatori sono naturali illimitati, ma non lo chiamerei più "assembly" - chiamerei URM o qualcosa del genere. In pratica, facciamo semplicemente finta che i limiti fisici siano abbastanza grandi da consentire l'esecuzione dei nostri programmi, quindi anche se un computer è solo una macchina a stati finiti (il che implica che non può ad esempio eseguire un'aggiunta), pensiamo più a questo come una macchina di Turing ( quindi l'aggiunta è fattibile).
chi,

4
@chi Questo non è abbastanza preciso. Prima di tutto, praticamente tutti considerano C come Turing completo perché in genere assegniamo quella frase attorno al presupposto che "hai abbastanza memoria". Per il numero molto limitato di persone che devono preoccuparsi della formulazione più rigorosa in cui tali presupposti non sono validi, C non specifica la dimensione di un puntatore e non specifica un limite a quanta memoria può essere indirizzata. Quindi, anche nel senso più rigoroso di "Turing completo", C è effettivamente Turing completo.
Cort Ammon,

3
@CortAmmon Non sono d'accordo. Se formalizzassimo la semantica di C, cercando di incorporare il presupposto della "memoria sufficiente", falliremmo dal momento che sizeof(void *)è tenuto a valutare qualcosa dallo standard ISO C. Questo ci costringe a limitare la quantità di memoria per un dato programma, a qualcosa di grande, ma ancora un limite. Ad esempio, non riesco a scrivere un programma la cui semantica sta aggiungendo due arbitrari naturali. C potrebbe ancora essere reso Turing potente tramite I / O, usando file come nastri TM (come sottolineato sopra da @Hurkyl). Concordo sul fatto che questo non costituisce un problema pratico.
chi,

7
Suggerisco il nuovo linguaggio C-inf, che è esattamente come C, tranne quando viene allocata troppa memoria attraverso la ricorsione o l'allocazione dell'heap, il programma viene interrotto, ricompilato con un valore maggiore per sizeof (void *) e sizeof (size_t), e ricomincia a correre dall'inizio.
gnasher729,

26

Pensa ai linguaggi di programmazione come a diversi veicoli terrestri: biciclette, automobili, hovercar, treni.

Turing Completezza dice "questo veicolo può andare ovunque qualunque altro veicolo possa andare". Cioè, puoi calcolare tutte le stesse funzioni. Ingresso per uscita, dall'inizio alla fine.

Ma questa affermazione non dice nulla su come ci si arriva. Potrebbe essere su rotaie, potrebbe essere su strade, potrebbe essere in aria. Allo stesso modo, Turing Completezza non dice nulla su come calcolare una funzione. È possibile utilizzare la ricorsione o l'iterazione o alcuni strani automi cellulari. È possibile utilizzare i tipi o meno, è possibile utilizzare tecniche dinamiche o statiche. Ma, se tutto ciò che consideri sono funzioni (o insiemi / linguaggi formali) puoi calcolare, finché sei Turing Complete, queste caratteristiche ti danno lo stesso potere.


4
Questa è una grande analogia. Si estende anche alla domanda che ho visto altrove in questo sito, se ci potrebbero essere o meno altri modelli di calcolo che superano una macchina Turing: in questa analogia, gli aeroplani e le astronavi sono più che Turing Complete, e i motoscafi sono un altro tipo di macchina interamente. :)
Wildcard il

2
Un viaggio più veloce della luce è probabilmente un'analogia migliore per il calcolo super-Turing. Esso può essere possibile, ma la maggior parte della gente pensa che non è.
jmite,

@jmite Naturalmente, non ci sono ancora buone prove che suggeriscano che i nostri cervelli siano computer super-turing. La nostra (apparente) incapacità di considerare le macchine non turing potrebbe derivare da ciò, sebbene non sia necessariamente una barriera insormontabile. Gli aeroplani sono in realtà una buona analogia: vanno semplicemente "dritti" tra due punti, ignorando il terreno. Se potessimo ignorare la topologia dello spazio-tempo stesso, potremmo volare anche più velocemente della luce. Non che io stia dicendo che è possibile ignorare la topologia dello spaziotempo,
intendiamoci

1
@Luaan Giusto, ma i nostri cervelli non devono necessariamente essere super-Turing per comprendere un computer super-Turing. Posso descrivere la semantica di una macchina di Turing usando un linguaggio di terminazione più debole, come Simply Typed Lambda Calculus, scrivendo una funzione che prende una TM e il suo stato e lo porta allo stato successivo. In realtà non riesco a far funzionare la macchina in quella lingua (perché potrebbe richiedere infiniti passaggi), ma posso scrivere come appare ogni passaggio.
jmite,

@Luaan, "non ci sono ancora buone prove che suggeriscano che i nostri cervelli siano computer super-turing" , forse, ma non ci sono prove che suggeriscano che la mente umana sia solo una macchina di Turing. Dal momento che non esiste una macchina di Turing che possa essere indirizzata verso qualsiasi punto che non possa essere ricondotto a idee originate da una mente umana, c'è ancora la distinzione che la vita può originare idee, e i meccanismi meccanici no. Ma per quanto riguarda i modelli di calcolo, penso che le macchine di Turing comprendano con successo tutto ciò che potrebbe ragionevolmente essere chiamato "calcolo", idee e sogni e simili.
Wildcard il

17

Ciò di cui stai essenzialmente chiedendo è la differenza tra il potere computazionale e quello che comunemente viene chiamato potere espressivo (o solo espressività ) di un linguaggio (o sistema di calcolo).

Potenza computazionale

Il potere computazionale si riferisce a quali tipi di problemi la lingua può calcolare. La classe di potenza computazionale più nota è quella equivalente a una macchina di Turing universale . Ci sono un sacco di altri sistemi di calcolo, come ad esempio Random Access Machines , λ-calcolo , SK combinatore calcolo , funzioni u-ricorsive , WHILEprogrammi, e molti altri. E a quanto pare, tutti questi possono simularsi a vicenda, il che significa che hanno tutti lo stesso potere computazionale.

Questo dà origine alla tesi di Church-Turing (che prende il nome dalla chiesa di Alonzo che ha creato il calcolo λ e Alan Turing che ha creato la macchina di Turing universale). La tesi di Church-Turing è un'ipotesi sulla computabilità con due aspetti:

  1. tutti i sistemi informatici in grado di calcolo generale sono ugualmente potenti e
  2. un essere umano che segue un algoritmo può calcolare esattamente le funzioni che una macchina di Turing (e quindi uno qualsiasi degli altri sistemi) può calcolare.

Il secondo è più importante nel campo della filosofia della mente rispetto all'informatica, però.

Tuttavia, ci sono due cose che la Church-Turing-Thesis non dice, che sono molto rilevanti per la tua domanda:

  1. quanto sono efficienti le varie simulazioni e
  2. quanto è conveniente la codifica di un problema.

Un semplice esempio di (1): su una macchina ad accesso casuale, la copia di un array richiede tempo proporzionale alla lunghezza dell'array. Su una macchina Turing, tuttavia, è necessario un tempo proporzionale al quadrato della lunghezza dell'array, poiché la macchina Turing non ha accesso casuale alla memoria, ma può spostarsi attraverso il nastro solo una cella alla volta. Pertanto, è necessario spostarsi tra gli n elementi dell'array n volte per copiarli. Pertanto, diversi modelli di calcolo possono avere caratteristiche prestazionali diverse, anche nel caso asintotico, in cui proviamo a sottrarci ai dettagli dell'implementazione.

Gli esempi di (2) abbondano: sia λ-calculus che Python sono Turing-complete. Ma preferiresti scrivere un programma in Python o in λ-calculus?

C'è anche una terza ruga che ho aggirato fino ad ora: tutti quei sistemi originali sono stati progettati da logici, filosofi o matematici, non da scienziati informatici ... semplicemente perché non esistevano i computer e quindi l'informatica. Tutto ciò risale ai primi anni '30, anche prima dei primi esperimenti di Konrad Zuse (che non erano programmabili e / o comunque completi da Turing). Parlano solo di "funzioni calcolabili sui numeri naturali".

Ora, a quanto pare, c'è molto che puoi esprimere come funzioni sui numeri naturali - dopo tutto, i nostri computer moderni riescono anche a cavarsela con molto meno (fondamentalmente 3-4 funzioni sui numeri 0 e 1, e basta ), ma, ad esempio, quale funzione calcola un sistema operativo?

Questa nozione di I / O, effetti collaterali, che interagiscono con l'ambiente, non viene catturata dall'idea di "funzioni su numeri naturali". Eppure, è un po 'importante, dal momento che, come disse una volta Simon Peyton Jones "Tutto ciò che una funzione pura senza effetti collaterali fa, è rendere la tua CPU calda" , a cui un membro del pubblico ha risposto "in realtà, questo è un lato -effetto anche! "

Edwin Brady , il progettista di Idris , (solo la metà) usa scherzosamente (non so se l'ha inventato) il termine "Tetris-completo" per esprimere questa differenza tra "può calcolare qualsiasi funzione calcolabile su numeri naturali" e "può essere utilizzato per scrivere programmi non banali che interagiscono con l'ambiente ". Ancora più ironicamente, lo dimostra avendo un'implementazione di un clone di Space Invaders in Idris , ma afferma di essere fiducioso che Tetris riduca a Space Invaders.

Un'altra cosa da sottolineare è che non solo l'equivalenza di Turing non è necessariamente sufficiente per parlare della scrittura di programmi "utili", ma potrebbe anche non essere necessario OTOH . Ad esempio, SQL è diventato equivalente a Turing solo con ANSI SQL: 1999 , ma prima era ancora utile. In effetti, alcuni potrebbero obiettare che renderlo equivalente a Turing non ha aggiunto nulla alla sua utilità. Esistono molte lingue specifiche del dominio che non sono equivalenti a Turing. Descrizione dei dati La lingua in genere non è (e non dovrebbe essere). Ovviamente Total Languages ​​non può essere equivalente a Turing, ma puoi comunque scrivere loop di eventi, web server o sistemi operativi in ​​essi. Ci sono anche lingue che sono equivalenti a Turing ma in cui questo è in realtà considerato un errore.

Quindi, tutto sommato, l'equivalenza di Turing non è terribilmente interessante, a meno che tu non voglia analizzare staticamente i programmi.

espressività

Supponendo che il nostro sistema di calcolo sia abbastanza potente dal punto di vista computazionale da risolvere il nostro problema, quello che dobbiamo fare dopo, è esprimere il nostro algoritmo per risolvere quel problema in una sorta di notazione formale per quel sistema. In altre parole: dobbiamo scrivere un programma in un linguaggio informatico. È qui che entra in gioco la nozione di espressività .

Si riferisce essenzialmente a quanto sia "facile" o "divertente" scrivere il nostro programma nel nostro particolare linguaggio di programmazione. Come puoi vedere, la nozione è piuttosto vaga, soggettiva e più psicologica che tecnica.

Tuttavia, ci sono tentativi di definizioni più precise. Il più famoso (e il più rigoroso che io conosca) è di Matthias Felleisen nel suo articolo Sul potere espressivo dei linguaggi di programmazione (le prime due pagine contengono un'introduzione delicata, il resto dell'articolo è più carnoso).

L'intuizione principale è questa: quando si traduce un programma dalla lingua in un'altra lingua, alcune delle modifiche che è necessario apportare sono contenute localmente (come ad esempio la trasformazione di FORloop in WHILEloop o loop in GOTOs condizionali ) e alcuni richiedono un cambiamento al globale struttura del programma.

Quando è possibile sostituire una caratteristica di una lingua con una diversa di una lingua diversa solo con trasformazioni locali, si dice che queste caratteristiche non hanno alcun effetto sul potere espressivo. Questo si chiama zucchero sintattico .

D'altra parte, se richiede un cambiamento della struttura globale del programma, si dice che la lingua che stai traducendo non è in grado di esprimere la funzione. E la lingua da cui stai traducendo si dice che sia più espressiva (rispetto a questa funzione).

Si noti che ciò fornisce una definizione oggettivamente misurabile di espressività. Si noti inoltre che la nozione dipende dal contesto della funzione ed è comparativa. Quindi, se ogni programma nella lingua A può essere tradotto nella lingua B con solo modifiche locali, e c'è almeno un programma nella lingua B che non può essere tradotto in A con solo modifiche locali, allora la lingua B è strettamente più espressiva della lingua UN. Tuttavia, lo scenario più probabile è che molti programmi in entrambe le lingue possano essere tradotti avanti e indietro, ma ci sono alcuni programmi in entrambe le lingue che non possono essere tradotti nell'altra. Ciò significa che nessuna delle due lingue è strettamente più espressiva dell'altra, hanno solo caratteristiche diverse che consentono di esprimere programmi diversi in modi diversi.

Ciò fornisce una definizione formale di cosa significhi essere "più espressivi", ma non cattura ancora le nozioni psicologiche alla base del fenomeno. Ad esempio, lo zucchero sintattico, secondo questo modello, non aumenta il potere espressivo di una lingua, perché può essere tradotto usando solo modifiche locali. Tuttavia, sappiamo per esperienza che avere FOR, WHILEe IFdisponibili, anche se sono solo zucchero sintattico per condizioni, GOTOrende più facile esprimere il nostro intento .

Il fatto è che lingue diverse hanno caratteristiche diverse che rendono più facile o difficile esprimere diversi modi di pensare a un problema. E alcune persone potrebbero trovare un modo per esprimere le proprie intenzioni più facilmente e altre in un modo diverso.

Un esempio che ho trovato nel tag Ruby su StackOverflow: molti utenti che seguono il tag Ruby affermano che i loop sono più facili da capire rispetto alla ricorsione e la ricorsione è solo per programmatori funzionali avanzati e i loop sono più intuitivi per i nuovi arrivati, ma ho visto più casi di completa i nuovi arrivati ​​che scrivono intuitivamente codice come questo:

def rock_paper_scissors
  get_user_input
  determine_outcome
  print_winner
  rock_paper_scissors # start from the top
end

Il che di solito porta molte persone a commentare che "questo non funziona" e "lo stanno facendo male" e il "modo corretto" è questo:

def rock_paper_scissors
  loop do
    get_user_input
    determine_outcome
    print_winner
  end
end

Quindi, chiaramente, ci sono alcune persone per le quali la ricorsione della coda è un modo più naturale per esprimere il concetto di "loop" rispetto ai costrutti di loop.

Sommario

Il fatto che due lingue siano equivalenti a Turing dice una e esattamente una cosa: che possono calcolare lo stesso insieme di funzioni su numeri naturali di una macchina di Turing. Questo è tutto.

Non dice nulla sulla velocità con cui calcolano quelle funzioni. Non dice nulla sulla facilità di esprimere quelle funzioni. E non dice altro su cos'altro possono fare oltre a calcolare le funzioni su numeri naturali (ad es. Collegamento a librerie C, lettura di input da parte dell'utente, scrittura di output sullo schermo).

ciò significa che la classe di problemi che ciascun linguaggio di programmazione può risolvere in modo accidentale varia in base al linguaggio, anche se questi linguaggi sono tutti completi?

Sì.

  1. Ci sono problemi che non sono coperti dal termine "Turing-complete" (che riguarda solo le funzioni di calcolo su numeri naturali) come la stampa sullo schermo. Due lingue possono essere complete di Turing ma una può consentire la stampa sullo schermo e l'altra no.
  2. Anche se entrambe le lingue possono risolvere gli stessi problemi, ciò non dice nulla su quanto sia complessa la codifica e quanto sia facile esprimere questa codifica. Ad esempio, C può risolvere ogni problema che Haskell può fare, semplicemente scrivendo un interprete Haskell in C ... ma devi prima scrivere l'interprete Haskell per risolvere un problema in questo modo!

7

Tutti i linguaggi di programmazione completi di Turing possono implementare lo stesso set di algoritmi. Quindi, se vedi che alcuni algoritmi sono molto difficili da implementare in un determinato linguaggio, ciò non significa che sia impossibile.

Ricorda che una lingua è composta da sintassi e semantica. A volte, l'insieme di parole appartenenti ad alcune lingue non è minimo da considerare Turing completo, ci sono funzionalità che rendono le cose più facili (ecco perché vengono chiamate funzioni ). Se si eliminano queste funzionalità, la lingua è ancora Turing completa.

Alcuni di questi potrebbero essere di interesse:


5

Tutte le lingue complete di Turing possono calcolare le stesse cose.

Se provi a implementare un linguaggio moderno, noterai che la maggior parte delle sue funzionalità non aggiunge alcuna capacità computazionale. Molte di queste funzionalità possono essere ridotte a più semplici già esistenti nella stessa lingua.

Ecco alcuni esempi:

  • Se non hai enumerazioni, puoi usare numeri interi.
  • Se non si dispone di buffer che ne conoscono le dimensioni, è possibile creare un tipo che contiene una dimensione e un buffer che non ne conosce le dimensioni.
  • Se non hai il controllo dei limiti dei buffer, puoi semplicemente controllare l'indice ogni volta che li usi.
  • Se non si hanno funzioni variadiche, è possibile creare una funzione che accetta un buffer che ne conosce le dimensioni e legge da quel buffer le stesse informazioni che si otterrebbero dagli argomenti formali.
  • Se non si dispone di operatori, è possibile utilizzare le funzioni.
  • Se non si hanno tipi che possono ereditarsi a vicenda, è possibile creare tipi che si contengano a vicenda e accedervi attraverso un ulteriore livello di riferimento indiretto, con il sottotipo che è semplicemente una trasformazione del tipo da gestire a un intero gestire-to-the-contained tipo.
  • Se non si dispone di delegati ma si dispone di puntatori a funzione, è possibile creare un tipo che contenga l'oggetto referente e un puntatore a funzione.
  • Se non si dispone di delegati ma si dispone di interfacce, è possibile dichiarare un'interfaccia che contiene un singolo metodo con la firma desiderata.
  • Se non si dispone di tipi generici, è possibile utilizzare tipi non generici che assumono solo i limiti superiore o inferiore a cui si è interessati (e forse eseguire cast appropriati nei punti di utilizzo, per rendere felice il compilatore).
  • Se non hai un sistema di tipo lineare / affine, puoi semplicemente evitare di utilizzare qualsiasi variabile più di una volta.
  • ...e così via.

Il design del linguaggio mainstream si concentra su funzionalità che ci rendono più semplice e conveniente calcolare le cose più velocemente, riconoscere i nostri errori in precedenza, programmare contro componenti sconosciuti, rendere più sicuro il parallelismo e così via.

Le cose puramente computazionali sono state inchiodate molto tempo fa.


4

Le risposte esistenti sottolineano giustamente che la completezza di Turing non è un buon modo per confrontare le lingue. In effetti, quasi tutte le lingue sono complete di Turing. ("Se tutti sono speciali, allora nessuno è speciale", come dicevano gli Incredibili.)

Tuttavia, è possibile confrontare l'espressività delle lingue con la precisione matematica. Dai un'occhiata al potere espressivo dei linguaggi di programmazione di Felleisen . All'incirca, l'idea è di porre la seguente domanda: Posso convertire qualsiasi programma in lingua A in un programma in lingua B apportando solo modifiche locali ? In altre parole, Felleisen dà una forma matematicamente precisa al tuo intuito.


2

Oltre alle risposte di tutti, ecco un'altra analogia.

Per lavare i vestiti, hai bisogno di tre cose: un serbatoio che contiene acqua, un detergente di qualche tipo e un meccanismo di agitazione. Questo potrebbe essere realizzato in molti modi. Il serbatoio dell'acqua è tutto ciò che contiene abbastanza acqua (ad esempio una vasca, un lago, un fiume). Il meccanismo di agitazione potrebbe essere un dispositivo meccanico, un ripiano o persino una roccia contro la quale vengono battuti i vestiti. E anche i detersivi sono disponibili in varie forme.

Quindi qual è la differenza tra una moderna lavatrice computerizzata e una roccia vicino a un fiume?

Si riduce a tre cose: efficienza, sicurezza e convenienza. Alcuni metodi di lavaggio consumano meno energia, inquinano meno l'ambiente, usano meno acqua e così via. Alcuni metodi di lavaggio richiedono attività manuali meno ripetitive (che provocano lesioni) o la presenza all'esterno in condizioni meteorologiche avverse. E alcuni metodi di lavaggio non richiedono che un essere umano faccia da babysitter al processo.

I linguaggi di programmazione completi di Turing sono di uso generale, quindi vengono assegnati a più di un compito. Tuttavia, per un determinato compito, alcuni linguaggi di programmazione sono più efficienti, più convenienti e più sicuri (nel senso che meno può andare storto quando il programma viene effettivamente utilizzato) rispetto ad altri.


2

Altri hanno fornito molte buone risposte, ma non menzionano esplicitamente un avvertimento che una volta mi confondeva molto: la completezza di Turing non implica che un linguaggio possa esprimere arbitrariamente funzioni calcolabili dai suoi input ai suoi output. È più debole: deve esserci un modo per rappresentare il dominio e la gamma dell'insieme di funzioni calcolabili come input e output in modo tale che ciascuna di queste funzioni si associ a un programma che porta una rappresentazione dei suoi input agli output corrispondenti.

Prendi, ad esempio, un linguaggio che esprime le macchine di Turing. Ogni programma nella lingua è una macchina di Turing.

Consideriamo ora la sotto lingua di tutte le macchine di Turing che leggono e scrivono solo i caratteri a, b e spazi vuoti. È Turing completo, ma non può esprimere alcun programma che, ad esempio, produca c su tutti gli input, perché non può scrivere alcun cs. Può solo esprimere tutte le funzioni calcolabili su input e output codificati come stringhe di as e bs.

Quindi non è vero che tutti i linguaggi completi di Turing possano calcolare le stesse cose, nemmeno quando limitiamo queste cose ad essere le funzioni calcolabili dai loro potenziali input ai loro potenziali output. La lingua potrebbe richiedere che gli input e gli output siano codificati in determinati modi.

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.