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 , WHILE
programmi, 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:
- tutti i sistemi informatici in grado di calcolo generale sono ugualmente potenti e
- 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:
- quanto sono efficienti le varie simulazioni e
- 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 FOR
loop in WHILE
loop o loop in GOTO
s 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
, WHILE
e IF
disponibili, anche se sono solo zucchero sintattico per condizioni, GOTO
rende 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ì.
- 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.
- 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!