J'ai rencontré un problème étrange lors de l'écriture d'un interpréteur qui (devrait) être raccordé à des programmes / fonctions externes: les fonctions en 'C' et 'C ++' ne peuvent pas accrocher les fonctions variadiques , par exemple, je ne peux pas créer de fonction qui appelle 'printf' avec exactement les mêmes arguments que ceux obtenus, et doit plutôt appeler une version alternative prenant un objet variadique. Ceci est très problématique car je veux pouvoir créer un objet contenant un crochet anonyme.
Donc, je pensais que c'était étrange depuis Forth , JavaScript , et peut-être une pléthore d'autres langages peuvent le faire très facilement sans avoir à recourir au code assembleur / code machine. Etant donné que d’autres langues peuvent le faire si facilement, cela signifie-t-il que la classe de problèmes que chaque langage de programmation peut résoudre varie en fait d’une langue à l’autre, même si ces langues sont toutes terminées ?
la source
Réponses:
Turing langages complets peuvent calculer le même ensemble de fonctions , qui est l'ensemble des fonctions partielles récursives générales. C'est ça.Nk→ N
Cela ne dit rien sur les fonctionnalités de la langue. Une machine de Turing a des caractéristiques de composition très limitées. Le calcul non typé est beaucoup plus compositionnel, mais il manque de nombreuses fonctionnalités couramment trouvées dans les langues modernes.λ
Cette exhaustivité ne dit rien sur les types, les tableaux / entiers / dictionnaires intégrés, les capacités d'entrée / sortie, l'accès réseau, le multithreading, l'allocation dynamique, ...
Tout simplement parce que Java n’a pas de fonctionnalité X (disons, macros, types de rang supérieur ou types dépendants), elle n’arrête pas subitement de devenir Turing complète.
La complétude et l'expressivité linguistique sont deux notions différentes.
la source
Cette complétude est un concept abstrait de calculabilité. Si un langage est complet de Turing, alors il est capable de faire n'importe quel calcul qu'un autre langage complet de Turing peut faire.
Cela ne dit cependant pas à quel point il est pratique de le faire. Certaines fonctionnalités faciles dans certaines langues peuvent être très difficiles dans d’autres, en raison des choix de conception. La complétude dit simplement que vous pouvez faire le calcul. Comme exemple extrême, il peut être difficile d’attacher des fonctions varadiques en C ++, mais il est possible d’écrire un interpréteur JavaScript en C ++ pouvant accrocher des fonctions variadiques.
Le design du langage est un art assez intéressant. L'une des principales étapes à entreprendre consiste à identifier les comportements sur lesquels vous souhaitez former l'épine dorsale de votre langage. Ces comportements sont des choses faciles à faire dans votre langue car ils sont intégrés au rez-de-chaussée. Nous prenons des décisions de conception sur les fonctionnalités à inclure dans toutes les langues.
En ce qui concerne votre exemple particulier, lorsque C a été développé, il était conçu pour fonctionner de manière très proche de celle des langages d’assemblage du jour. Les fonctions variadiques ont simplement poussé des arguments sur la pile, avec très peu de sécurité de types. La mise en œuvre de ces fonctions variadiques a été laissée au compilateur afin d’assurer une portabilité maximale. En conséquence, très peu d'hypothèses ont été formulées sur les capacités du matériel. Au moment où JavaScript est arrivé, la scène avait changé. Il fonctionne déjà dans une machine virtuelle en tant que langage interprété, de sorte que la balance évolue vers la commodité. Autoriser l'accrochage de fonctions variadiques devient raisonnable. Même dans le cas de JavaScript compilé Just In Time, nos compilateurs sont disposés à stocker beaucoup plus d'informations supplémentaires sur les arguments que les compilateurs C de jadis.
la source
sizeof(void *)
est tenu d'évaluer la norme ISO C. Cela nous oblige à limiter la quantité de mémoire pour un programme donné, à quelque chose de grand - mais toujours limité. Par exemple, je ne peux pas écrire un programme dont la sémantique ajoute deux éléments naturels arbitraires. C peut toujours être rendu Turing puissant via les E / S, en utilisant des fichiers tels que des bandes TM (comme le fait remarquer @Hurkyl ci-dessus). Je suis d'accord pour dire qu'il ne s'agit pas d'un problème en pratique.Pensez aux langages de programmation comme à différents véhicules terrestres: vélos, voitures, aéroglisseurs, trains.
Turing Completeness indique que "ce véhicule peut aller où n'importe quel autre véhicule peut aller." C'est-à-dire que vous pouvez calculer toutes les mêmes fonctions. Entrée à sortie, début à la fin.
Mais, cette déclaration ne dit rien sur la façon dont vous y parvenez. Ce peut être sur des rails, sur des routes, dans les airs. De la même manière, Turing Completeness ne dit rien sur la façon dont vous calculez une fonction. Vous pouvez utiliser la récursivité, ou l'itération, ou des automates cellulaires étranges. Vous pouvez utiliser des types ou non, vous pouvez utiliser des techniques dynamiques ou statiques. Mais si vous ne tenez compte que des fonctions (ou des ensembles / langages formels) que vous pouvez calculer, à condition que vous soyez Turing Complete, ces fonctionnalités vous donnent le même pouvoir.
la source
Ce que vous demandez essentiellement, c’est la différence entre le pouvoir de calcul et ce que l’on appelle communément le pouvoir d’expression (ou simplement l’ expression ) d’un langage (ou d’un système de calcul).
Puissance de calcul
Le pouvoir de calcul fait référence aux types de problèmes que le langage peut traiter. La classe de puissance de calcul la plus connue est celle qui équivaut à une machine de Turing universelle . Il existe de nombreux autres systèmes de calcul, tels que les machines à accès aléatoire , le λ-calcul , le calculateur combinatoire SK , les fonctions µ-récursives , les
WHILE
programmes et bien d'autres. Et il s'avère que tous peuvent se simuler, ce qui signifie qu'ils ont tous la même puissance de calcul.Cela donne lieu à la thèse de Church-Turing (nommée d'après Alonzo Church qui a créé le λ-calcul et Alan Turing qui a créé la machine de Turing universelle). The Church-Turing-Thesis est une hypothèse sur la calculabilité à deux aspects:
La seconde est plus importante dans le domaine de la philosophie de l’esprit que dans la science informatique.
Cependant, la thèse de l'Église de Turing ne dit pas deux choses qui sont très pertinentes pour votre question:
Un exemple simple pour (1): sur un ordinateur à accès aléatoire, la copie d'un tableau prend du temps proportionnellement à sa longueur. Sur une machine Turing, cependant, cela prend du temps proportionnellement au carré de la longueur de la matrice, car la machine Turing n’a pas d’accès mémoire aléatoire, elle ne peut se déplacer sur la bande, cellule par cellule. Par conséquent, il doit se déplacer n fois sur les n éléments du tableau pour les copier. Ainsi, différents modèles de calcul peuvent avoir différentes caractéristiques de performance, même dans le cas asymptotique, où nous essayons d’abstraire les détails de la mise en oeuvre.
Les exemples pour (2) abondent: λ-calcul et Python sont tous deux Turing-complete. Mais préférez-vous écrire un programme en Python ou en λ-calcul?
J'ai également contourné une troisième ride jusqu'à présent: tous ces systèmes originaux ont été conçus par des logiciens, des philosophes ou des mathématiciens, et non par des informaticiens… simplement parce que les ordinateurs et donc l'informatique n'existaient pas. Tout cela remonte au début des années 1930, avant même les toutes premières expériences de Konrad Zuse (qui n'étaient de toute façon pas programmables et / ou Turing complètes). Ils ne parlent que de "fonctions calculables sur les nombres naturels".
Il se trouve qu’il ya beaucoup de choses que vous pouvez exprimer comme fonctions sur les nombres naturels - après tout, nos ordinateurs modernes se débrouillent même avec beaucoup moins que cela (essentiellement 3-4 fonctions sur les nombres 0 et 1, et c’est tout ), mais par exemple, quelle fonction un système d’exploitation calcule-t-il?
Cette notion d'E / S, d'effets secondaires, en interaction avec l'environnement, n'est pas capturée par l'idée de "fonctions sur des nombres naturels". Et pourtant, il est assez important, puisque, comme Simon Peyton Jones a dit une fois « Toute une fonction pure, sans effets secondaires ne, est de rendre votre CPU chaud » , auquel un membre du public a répondu « En fait, c'est un côté -Effet aussi! "
Edwin Brady , le concepteur d’ Idris , (seulement la moitié) utilise en plaisantant (je ne sais pas s’il l’a inventé) le terme "Tetris-complete" pour exprimer cette différence entre "peut calculer toute fonction calculable sur des nombres naturels" et "peut être utilisé pour écrire des programmes non-triviaux qui interagissent avec l'environnement ". Encore plus ironiquement, il le démontre en mettant en œuvre un clone de Space Invaders dans Idris , mais il se dit confiant que Tetris se réduit à Space Invaders.
Une autre chose à souligner est que non seulement l'équivalence de Turing n'est pas nécessairement suffisante pour parler d'écriture de programmes "utiles", elle peut aussi ne pas être nécessaire . Par exemple, SQL n'est devenu équivalent à Turing qu'avec ANSI SQL: 1999 , mais il était encore utile auparavant. En fait, certains pourraient dire que le fait de rendre l'équivalent de Turing n'a pas du tout augmenté son utilité. Il existe de nombreux langages spécifiques à un domaine qui ne sont pas équivalents à Turing. Le langage de description de données n'est généralement pas (et ne devrait pas l'être). Total Languages ne peut évidemment pas être équivalent à Turing, mais vous pouvez toujours y écrire des boucles d’événements, des serveurs Web ou des systèmes d’exploitation. Il existe également des langues équivalentes à Turing, mais considérées comme une erreur.
Donc, dans l’ensemble, l’équivalence de Turing n’est pas très intéressante, à moins que vous ne souhaitiez analyser statiquement des programmes.
Expressivité
En supposant que notre système de calcul soit assez puissant pour résoudre même notre problème, nous devons ensuite exprimer notre algorithme de résolution du problème dans une sorte de notation formelle pour ce système. En d'autres termes: nous devons écrire un programme dans un langage informatique. C'est là qu'intervient la notion d' expressivité .
Il s'agit essentiellement de la "facilité" ou du "plaisir" d'écrire notre programme dans notre langage de programmation particulier. Comme vous pouvez le constater, la notion est assez vague, subjective et plus psychologique que technique.
Cependant, il existe des tentatives de définitions plus précises. Le plus célèbre (et le plus rigoureux que je connaisse) est de Matthias Felleisen dans son article Sur le pouvoir expressif des langages de programmation (les deux premières pages contiennent une introduction douce, le reste du document est plus charnu).
L’intuition principale est la suivante: lors de la traduction d’un programme d’une langue à une autre, certains des changements que vous devez faire sont localisés (par exemple, la conversion de
FOR
boucles enWHILE
boucles ou de boucles en conditionnellesGOTO
), et certaines nécessitent un changement de structure du programme.Lorsque vous pouvez remplacer une caractéristique d'une langue par une autre, d'une transformation différente, par des transformations locales uniquement, ces fonctionnalités sont réputées n'avoir aucun effet sur le pouvoir d'expression. C'est ce qu'on appelle le sucre syntaxique .
D'autre part, si cela nécessite un changement de la structure globale du programme, la langue dans laquelle vous traduisez est dite incapable d'exprimer la fonctionnalité. Et la langue à partir de laquelle vous traduisez est dite plus expressive (en ce qui concerne cette fonctionnalité).
Notez que cela donne une définition de l'expressivité objectivement mesurable. Notez également que la notion dépend du contexte de la fonctionnalité et qu'elle est comparative. Ainsi, si chaque programme de la langue A peut être traduit en langue B avec uniquement des modifications locales et s'il existe au moins un programme en langue B qui ne peut pas être traduit en A avec des modifications locales uniquement, la langue B est strictement plus expressive que la langue. UNE. Cependant, le scénario le plus probable est que de nombreux programmes dans les deux langues peuvent être traduits, mais il existe certains programmes dans les deux langues qui ne peuvent pas être traduits dans une autre. Cela signifie qu’aucun langage n’est strictement plus expressif que l’autre, ils ont juste des caractéristiques différentes qui permettent à différents programmes de s’exprimer de différentes manières.
Cela donne une définition formelle de ce que signifie être "plus expressif", mais ne capture toujours pas les notions psychologiques à la base du phénomène. Par exemple, le sucre syntaxique, selon ce modèle, n'augmente pas le pouvoir d'expression d'une langue, car il peut être traduit en utilisant uniquement des modifications locales. Cependant, nous savons par expérience que ayant
FOR
,WHILE
etIF
disponibles, même si elles ne sont que du sucre syntaxique pour conditionnelGOTO
marques exprimant notre intention plus facile .Le fait est que différentes langues ont des caractéristiques différentes qui permettent d’exprimer différentes façons de penser à un problème plus facilement ou plus difficilement. Et certaines personnes trouveront peut-être un moyen d’exprimer leur intention plus facilement et d’autres, d’une manière différente.
Un exemple que j’ai trouvé dans la balise Ruby sur StackOverflow: de nombreux utilisateurs qui suivent la balise Ruby affirment que les boucles sont plus faciles à comprendre que la récursion et que la récursivité n’est que pour les programmeurs fonctionnels avancés et que les boucles sont plus intuitives pour les nouveaux arrivants, mais j’ai vu plusieurs cas de les nouveaux venus qui écrivent intuitivement un code comme celui-ci:
Ce qui conduit généralement plusieurs personnes à commenter que "cela ne fonctionne pas" et "elles le font mal" et que la "manière correcte" est la suivante:
Il est donc clair que, pour certaines personnes, la récursion de la queue est un moyen plus naturel d’exprimer le concept de "bouclage" que les constructions en boucle.
Sommaire
Le fait que deux langues soient équivalentes à Turing en dit long sur le fait qu’elles peuvent calculer le même ensemble de fonctions sur des nombres naturels qu’une machine de Turing. C'est ça.
Cela ne dit rien sur la rapidité avec laquelle ils calculent ces fonctions. Cela ne dit rien sur la facilité à exprimer ces fonctions. Et cela ne dit rien sur ce qu’ils peuvent faire en plus des fonctions de calcul sur les nombres naturels (par exemple, la liaison à des bibliothèques C, la lecture des entrées de l’utilisateur, l’écriture de la sortie sur l’écran).
Oui.
la source
Tous les langages de programmation complets de Turing peuvent implémenter le même ensemble d'algorithmes. Donc, si vous voyez qu'un algorithme est très difficile à implémenter dans un langage particulier, cela ne veut pas dire que c'est impossible.
Rappelez-vous qu'un langage est constitué de syntaxe et de sémantique. Parfois, l’ensemble des mots appartenant à une langue donnée n’est pas minimal pour être considéré comme complet, il existe des fonctionnalités qui facilitent les choses (c’est pourquoi elles sont appelées fonctionnalités ). Si vous supprimez ces fonctionnalités, le langage est toujours complet.
Certains d'entre eux pourraient être d'intérêt:
Compilateur source à source sur Wikipedia
Sur l'importance de la complétude de Turing sur Lambda the Ultimate
la source
Tous les langages complets de Turing peuvent calculer les mêmes choses.
Si vous essayez de mettre en œuvre une langue moderne, vous remarquerez que la plupart de ses caractéristiques n'ajouter des capacités de calcul. Beaucoup de ces fonctionnalités peuvent être réduites à des fonctionnalités plus simples qui existent déjà dans le même langage.
Voici quelques exemples:
La conception linguistique standard se concentre sur des fonctionnalités qui facilitent et facilitent le calcul, l’identification précoce des erreurs, la programmation contre des composants inconnus, la sécurisation du parallélisme, etc.
Les éléments purement informatiques ont été cloués il y a longtemps.
la source
Les réponses existantes indiquent à juste titre que l’exhaustivité de Turing n’est pas un bon moyen de comparer des langues. En effet, presque toutes les langues sont complètes. ("Si tout le monde est spécial, personne n'est spécial", disait The Incredibles.)
Cependant, il est possible de comparer l'expressivité des langues avec la précision mathématique. Jetez un coup d'œil sur le pouvoir expressif des langages de programmation de Felleisen . En gros, l’idée est de poser la question suivante: Puis-je convertir n’importe quel programme de la langue A en un programme de la langue B en n’effectuant que des modifications locales ? En d'autres termes, Felleisen donne une forme mathématiquement précise à votre intuition.
la source
En plus des réponses de tous les autres, voici une autre analogie.
Pour laver les vêtements, vous avez besoin de trois choses: un réservoir contenant de l’eau, un détergent quelconque et un mécanisme d’agitation. Cela pourrait être réalisé à bien des égards. Le réservoir d'eau est tout ce qui contient suffisamment d'eau (par exemple une baignoire, un lac, une rivière). Le mécanisme d'agitation pourrait être un dispositif mécanique, une planche à laver ou même un rocher contre lequel les vêtements sont frappés. Et les détergents se présentent également sous différentes formes.
Alors, quelle est la différence entre une machine à laver moderne informatisée et un rocher au bord d'une rivière?
Cela se résume à trois choses: efficacité, sécurité et commodité. Certaines méthodes de lavage consomment moins d'énergie, polluent moins l'environnement, consomment moins d'eau, etc. Certaines méthodes de lavage nécessitent des activités manuelles moins répétitives (qui entraînent des blessures) ou d'être à l'extérieur par mauvais temps. Et certaines méthodes de lavage n'exigent pas qu'un humain surveille le processus.
Les langages de programmation Turing-complete ont une utilité générale et sont donc soumis à plusieurs tâches. Néanmoins, pour une tâche donnée, certains langages de programmation sont plus efficaces, plus pratiques et plus sûrs (en ce sens que moins de problèmes peuvent survenir lorsque le programme est réellement utilisé) que d’autres.
la source
D'autres ont fourni beaucoup de bonnes réponses, mais ils ne mentionnent pas explicitement une mise en garde qui m'avait beaucoup déroutée une fois: le fait d'être complet ne signifie pas qu'un langage peut exprimer des fonctions calculables arbitraires à partir de ses entrées vers ses sorties. Il est plus faible: il doit exister un moyen de représenter le domaine et la plage de l’ensemble des fonctions calculables en tant qu’entrées et sorties de sorte que chacune de ces fonctions mappe à un programme qui prend une représentation de ses entrées en sorties correspondantes.
Prenons, par exemple, un langage qui exprime les machines de Turing. Chaque programme dans la langue est une machine de Turing.
Maintenant, considérons la sous-langue de toutes les machines Turing qui lisent et écrivent uniquement les caractères a, b et les blancs. Il est complet, mais il ne peut exprimer aucun programme produisant, par exemple, c sur toutes les entrées, car il ne peut écrire aucun cs. Il ne peut exprimer que toutes les fonctions calculables sur les entrées et les sorties codées sous forme de chaînes de caractères as et bs.
Il n'est donc pas vrai que tous les langages complets de Turing peuvent calculer les mêmes choses, pas même lorsque nous limitons ces choses aux fonctions calculables de leurs entrées potentielles à leurs sorties potentielles. Le langage peut nécessiter que les entrées et les sorties soient codées de certaines manières.
la source