Je lisais l'article ici: http://www.paulgraham.com/avg.html et la partie sur le "paradoxe du blub" était particulièrement intéressante. En tant que personne qui code principalement en c ++ mais qui est exposée à d’autres langages (principalement Haskell), je connais quelques points utiles dans ces langages qui sont difficiles à répliquer en c ++. La question s'adresse principalement aux personnes qui maîtrisent à la fois le c ++ et un autre langage. Existe-t-il une fonctionnalité ou un langage puissant que vous utilisez dans un langage qui serait difficile à conceptualiser ou à mettre en œuvre si vous écriviez uniquement en c ++?
En particulier, cette citation a attiré mon attention:
Par induction, les seuls programmeurs capables de voir toutes les différences de pouvoir entre les différents langages sont ceux qui comprennent le plus puissant. (C’est probablement ce que Eric Raymond voulait dire: Lisp ferait de vous un meilleur programmeur.) Vous ne pouvez pas vous fier aux opinions des autres, à cause du paradoxe Blub: ils sont satisfaits de la langue qu’ils utilisent, car elle dicte la façon dont ils pensent aux programmes.
S'il s'avère que je suis l'équivalent du programmeur "Blub" en vertu de l'utilisation de c ++, la question suivante se pose: Existe-t-il des concepts ou techniques utiles que vous auriez rencontrés dans d'autres langues et qu'il vous aurait été difficile de conceptualiser si écrit ou "pense" en c ++?
Par exemple, le paradigme de programmation logique utilisé dans des langages tels que Prolog et Mercury peut être implémenté en c ++ à l'aide de la bibliothèque castor, mais je trouve finalement que je pense conceptuellement en termes de code Prolog et que je traduis en équivalent c ++ lorsque je l'utilise. Afin d’élargir mes connaissances en programmation, je cherche à savoir s’il existe d’autres exemples similaires d’idiomes utiles / puissants qui sont exprimés plus efficacement dans d’autres langages que je ne connais peut-être pas en tant que développeur c ++. Un autre exemple qui me vient à l’esprit est le système de macros dans lisp. Générer le code du programme à partir de celui-ci semble présenter de nombreux avantages pour certains problèmes. Cela semble difficile à implémenter et à penser à partir de c ++.
Cette question n'est pas destinée à être un débat "c ++ vs lisp" ou un débat de type guerres de langage. Poser une question comme celle-ci est le seul moyen, à mon avis, de découvrir des choses que je ne connais pas et que je ne connais pas.
la source
there are things that other languages can do that Lisp can't
- Peu probable, puisque Lisp est complet. Peut-être avez-vous voulu dire qu'il y a des choses qui ne sont pas pratiques à faire à Lisp? Je pourrais dire la même chose de n'importe quel langage de programmation.Réponses:
Eh bien, puisque vous avez mentionné Haskell:
Correspondance de modèle. Je trouve que la correspondance des motifs est beaucoup plus facile à lire et à écrire. Réfléchissez à la définition de la carte et à la manière dont elle serait mise en œuvre dans une langue sans correspondance de modèle.
Le système de types. Cela peut être une douleur parfois mais c'est extrêmement utile. Vous devez programmer avec pour bien le comprendre et combien de bugs il attrape. En outre, la transparence référentielle est merveilleuse. Cela ne devient apparent qu'après un certain temps de programmation en Haskell combien de bogues sont causés par la gestion d'état dans un langage impératif.
Programmation fonctionnelle en général. Utiliser des cartes et des plis au lieu d'itération. Récursion. Il s'agit de penser à un niveau supérieur.
Évaluation paresseuse. Encore une fois, il s’agit de penser à un niveau supérieur et de laisser le système gérer l’évaluation.
Cabale, forfaits et modules. Avoir Cabal télécharge des paquets pour moi, c'est beaucoup plus pratique que de chercher du code source, d'écrire un fichier makefile, etc. Pouvoir importer uniquement certains noms est beaucoup mieux que d'avoir essentiellement tous les fichiers source vides, puis compilés.
la source
Maybe
(pour C ++, voirstd::optional
), mais d'avoir à marquer explicitement les choses comme optionnel / nullable / peut-être.Mémoize!
Essayez de l'écrire en C ++. Pas avec C ++ 0x.
Trop lourd? Ok, essayez-le avec C ++ 0x.
Voyez si vous pouvez battre cette version de compilation à 4 lignes (ou 5 lignes, peu importe: P) en D:
Tout ce que vous devez faire pour l'appeler est quelque chose comme:
Vous pouvez également essayer quelque chose de similaire dans Scheme, bien que ce soit un peu plus lent car cela se produit au moment de l'exécution et parce que la recherche ici est linéaire au lieu de hachée (et bien, parce que c'est Scheme):
la source
C ++ est un langage multiparadigmique, ce qui signifie qu’il essaie de supporter de nombreuses façons de penser. Parfois, une fonctionnalité C ++ est plus maladroite ou moins fluide qu'une implémentation d'un autre langage, comme c'est le cas avec la programmation fonctionnelle.
Cela dit, je ne peux pas me faire une idée en tête d’une fonctionnalité native du langage C ++ qui fait ce que fait le
yield
langage Python ou JavaScript.Un autre exemple est la programmation simultanée . C ++ 0x aura son mot à dire à ce sujet, mais pas le standard actuel, et l'accès simultané est une toute nouvelle façon de penser.
En outre, le développement rapide - même la programmation shell - est une chose que vous ne saurez jamais si vous ne quittez jamais le domaine de la programmation C ++.
la source
setjmp
etlongjmp
. Je ne sais pas du tout ce qui se casse, mais je suppose que des exceptions seraient les premières à disparaître. Maintenant, si vous voulez bien m'excuser, il faut que je relise Modern C ++ Design pour m'en sortir.Les Coroutines sont une fonctionnalité linguistique extrêmement utile qui sous-tend de nombreux avantages plus tangibles des autres langages que le C ++. En gros, ils fournissent des piles supplémentaires pour que les fonctions puissent être interrompues et poursuivies, fournissant des fonctionnalités similaires à celles d'un pipeline pour la langue, qui permettent d'alimenter facilement les résultats des opérations en filtrant d'autres opérations. C'est merveilleux et dans Ruby, je l'ai trouvé très intuitif et élégant. L'évaluation paresseuse est également liée à cela.
Introspection et compilation / exécution / évaluation de code à l'exécution, quelles que soient les fonctionnalités massivement puissantes qui manquent à C ++.
la source
Ayant implémenté un système de calcul formel à la fois en Lisp et en C ++, je peux vous dire que la tâche était beaucoup plus facile en Lisp, même si j’étais un novice du langage. Cette nature simpliste de toutes les listes en cours simplifie un grand nombre d’algorithmes. Certes, la version C ++ a été zillion de fois plus rapide. Oui, j'aurais pu rendre la version de Lisp plus rapide, mais le code ne serait pas aussi lisible. L'écriture de scripts est une autre chose qui sera toujours plus facile, par exemple lisp. Il s'agit d'utiliser le bon outil pour le travail.
la source
Que veut-on dire quand on dit qu'une langue est "plus puissante" qu'une autre? Quand on dit qu'un langage est "expressif?" Ou "riche?" Je pense que nous entendons par là qu’une langue gagne en puissance lorsque son champ de vision se rétrécit suffisamment pour rendre facile et naturelle la description d’un problème - une transition d’état, non? - qui vit dans cette vue. Pourtant, ce langage est considérablement moins puissant, moins expressif et moins utile lorsque notre champ de vision s’élargit.
Plus le langage est "puissant" et "expressif", plus son usage est limité. Alors peut-être "puissant" et "expressif" sont les mauvais mots à utiliser pour un outil d’utilité étroite. Peut-être "approprié" ou "abstrait" sont de meilleurs mots pour de telles choses.
J'ai commencé par programmer en écrivant tout un tas de choses de bas niveau: des pilotes de périphériques avec leurs routines d'interruption; programmes intégrés; code du système d'exploitation. Le code était intime avec le matériel et j'ai tout écrit en langage assembleur. Nous ne dirions pas que cet assembleur est au moins abstrait, pourtant il était et reste le langage le plus puissant et le plus expressif de tous. Je peux exprimer n'importe quel problème en langage assembleur; c'est tellement puissant que je peux faire tout ce que je veux avec n'importe quelle machine.
Et toute ma compréhension ultérieure du langage de niveau supérieur doit tout à mon expérience avec assembleur. Tout ce que j'ai appris par la suite a été facile car, vous voyez, tout - même abstrait - doit finalement s'adapter au matériel.
Vous voudrez peut-être oublier les niveaux d'abstraction de plus en plus élevés, c'est-à-dire les champs de vision de plus en plus étroits. Vous pouvez toujours le reprendre plus tard. C'est un jeu d'enfant à apprendre, quelques jours. À mon avis, vous feriez mieux d’apprendre le langage du matériel 1 , de vous rapprocher le plus possible de l’os.
1 Peut-être pas tout à fait germane, mais
car
etcdr
prendre leurs noms à partir du matériel: la première Lisp a couru sur une machine qui avait une réelle Décrémenter Inscrivez -vous et une adresse réelle registre. Comment ça?la source
Tableaux associatifs
Un moyen typique de traiter les données est:
Le bon outil est le tableau associatif .
Je n'aime pas vraiment la syntaxe de tableau associatif de JavaScript, car je ne peux pas créer, disons un [x] [y] [z] = 8 , je dois d'abord créer un [x] et un [x] [y] .
D'accord, en C ++ (et en Java), il existe un beau portefeuille de classes de conteneurs, Map , Multimap , que ce soit, mais si je veux parcourir, je dois faire un itérateur, et quand je veux insérer un nouvel élément de niveau profond, je doivent créer tous les niveaux supérieurs, etc. inconfortable.
Je ne dis pas qu'il n'y a pas de tableaux associatifs utilisables en C ++ (et Java), mais les langages de script non typés (ou non typés strict) surpassent les langages compilés, car ce sont des langages de script non typés.
Avertissement: Je ne suis pas familier avec C # et les autres langages .NET, autant que je sache, ils ont une bonne gestion des tableaux associatifs.
la source
dict
(par exemplex = {0: 5, 1: "foo", None: 500e3}
, notez qu'il n'est pas nécessaire que les clés ou les valeurs soient du même type). Il est difficile d’essayer de faire quelque chose du genre,a[x][y][z] = 8
car le langage doit regarder vers l’avenir pour voir si vous allez définir une valeur ou créer un autre niveau; l'expressiona[x][y]
en elle-même ne vous dit pas.Je n'ai pas appris Java, C \ C ++, Assembly et Java Script. J'utilise C ++ pour gagner ma vie.
Cependant, je dois dire que j'aime davantage la programmation d'assemblage et la programmation C. Ceci est principalement lié à la programmation impérative.
Je sais que les paradigmes de programmation sont importants pour catégoriser les types de données et donner des concepts abstraits de programmation supérieure pour permettre des modèles de conception puissants et une formalisation du code. Dans un sens, chaque paradigme est une collection de motifs et de collections permettant d’abréger la couche matérielle sous-jacente, de sorte que vous n’avez pas besoin de penser à EAX ou à l’IP en interne dans la machine.
Mon seul problème avec cela, est-ce que cela permet de transformer la notion de peuple et les concepts de fonctionnement de la machine en assertions idéologiques et ambiguës de ce qui se passe. Ce pain contient toutes sortes d'abstractions merveilleuses qui s'ajoutent aux résumés d'un objectif idéologique du programmeur.
À la fin de la journée, il est préférable d’avoir un état d’esprit clair et de définir les limites du processeur et du fonctionnement des ordinateurs. Tout ce qui importe au processeur, c’est d’exécuter une série d’instructions qui déplacent des données en mémoire vers un registre et les exécute. Il n'a pas de concept de type de données, ni de concepts de programmation supérieurs. Il ne fait que déplacer des données.
Cela devient plus complexe lorsque vous ajoutez des paradigmes de programmation dans la combinaison, car notre vision du monde est différente.
la source
C ++ rend de nombreuses approches intraitables. J'irais même jusqu'à dire que la plupart des programmes sont difficiles à conceptualiser si vous vous limitez au C ++. Voici quelques exemples de problèmes qui sont beaucoup plus facilement résolus d'une manière que C ++ rend difficile.
Enregistrement des conventions d'allocation et d'appel
Beaucoup de gens pensent que C ++ est un langage bas niveau, mais ce n’est vraiment pas le cas. En faisant abstraction des détails importants de la machine, C ++ rend difficile la conceptualisation de fonctionnalités telles que l'allocation de registres et les conventions d'appel.
Pour en savoir plus sur des concepts tels que ceux-ci, je vous recommande de vous initier à la programmation en langage assembleur et de consulter cet article sur la qualité de la génération de code ARM .
Génération de code à l'exécution
Si vous ne connaissez que le C ++, alors vous pensez probablement que les modèles sont la base de la métaprogrammation. Ils ne sont pas. En fait, ils constituent un outil objectivement mauvais pour la métaprogrammation. Tout programme manipulant un autre programme est un métaprogramme, comprenant des interprètes, des compilateurs, des systèmes de calcul formel et des démonstrateurs de théorèmes. La génération de code à l'exécution est une fonctionnalité utile pour cela.
Je recommande de lancer une implémentation Scheme et de jouer avec
EVAL
pour en apprendre davantage sur l'évaluation métacirculaire.Manipuler des arbres
Les arbres sont partout dans la programmation. En analysant, vous avez des arbres de syntaxe abstraite. Dans les compilateurs, vous avez des RI qui sont des arbres. Dans les graphiques et la programmation graphique, vous avez des arbres de scène.
Cet "analyseur JSON ridiculement simple pour C ++" ne pèse que 484 LOC, ce qui est très petit pour C ++. Maintenant, comparez-le avec mon propre analyseur JSON simple, qui pèse seulement 60 LOC de F #. La différence tient principalement au fait que les types de données algébriques de ML et la correspondance de modèles (y compris les modèles actifs) facilitent considérablement la manipulation des arbres.
Découvrez également les arbres rouge-noir dans OCaml .
Structures de données purement fonctionnelles
L'absence de GC en C ++ rend pratiquement impossible l'adoption de certaines approches utiles. Les structures de données purement fonctionnelles sont l'un de ces outils.
Par exemple, consultez ce matcher d’expression régulière de 47 lignes dans OCaml. La brièveté est due en grande partie à l'utilisation intensive de structures de données purement fonctionnelles. En particulier, l'utilisation de dictionnaires avec des clés définies. C'est vraiment difficile à faire en C ++ car les dictionnaires et les ensembles stdlib sont tous mutables, mais vous ne pouvez pas muter les clés d'un dictionnaire ou casser la collection.
La programmation logique et les tampons d'annulation sont d'autres exemples pratiques dans lesquels des structures de données purement fonctionnelles rendent la tâche difficile en C ++ très facile dans d'autres langages.
Appels de queue
Non seulement C ++ ne garantit pas les appels finaux, mais RAII est fondamentalement en désaccord avec lui parce que les destructeurs gênent les appels en position finale. Les appels de queue vous permettent d'effectuer un nombre illimité d'appels de fonction en utilisant uniquement une quantité limitée d'espace de pile. C'est parfait pour la mise en œuvre de machines d'état, y compris de machines d'état extensibles, et c'est une excellente carte "sortir de prison" dans de nombreuses circonstances contraignantes.
Par exemple, vérifiez cette implémentation du problème de sac à dos 0-1 en utilisant le style de continuation-pass avec la mémorisation en F # du secteur financier. Lorsque vous avez des appels en queue, le style de transmission continue peut être une solution évidente, mais C ++ le rend insoluble.
Simultanéité
Un autre exemple évident est la programmation simultanée. Bien que cela soit tout à fait possible en C ++, il est extrêmement sujet aux erreurs par rapport à d'autres outils, notamment la communication de processus séquentiels, comme dans des langages comme Erlang, Scala et F #.
la source
C'est une vieille question, mais comme personne ne l'a mentionnée, j'ajouterai des compréhensions de liste (et maintenant de dict). Il est facile d'écrire un one-liner en Haskell ou en Python qui résout le problème de Fizz-Buzz. Essayez de faire cela en C ++.
Bien que C ++ ait considérablement évolué vers la modernité avec C ++ 11, il est plutôt difficile de l'appeler un langage "moderne". C ++ 17 (qui n'a pas encore été publié) fait encore plus de démarches pour se conformer aux normes modernes, tant que "moderne" signifie "pas du millénaire précédent".
Même la plus simple des compréhensions que l'on puisse écrire sur une seule ligne en Python (et obéissant à la limite de 79 caractères de Guido) devient une multitude de lignes de codes lors de la traduction en C ++, et certaines de ces lignes de code C ++ sont plutôt alambiquées.
la source
Une bibliothèque compilée appelant un rappel, qui est une fonction membre définie par l'utilisateur d'une classe définie par l'utilisateur.
Ceci est possible dans Objective-C et facilite grandement la programmation de l'interface utilisateur. Vous pouvez dire à un bouton: "Appelez cette méthode pour cet objet lorsque vous cliquez dessus", et le bouton le fera. Vous êtes libre d'utiliser n'importe quel nom de méthode pour le rappel que vous aimez, il n'est pas figé dans le code de la bibliothèque, vous ne devez pas hériter d'un adaptateur pour que cela fonctionne, et le compilateur ne veut pas résoudre l'appel au moment de la compilation, et, tout aussi important, vous pouvez indiquer à deux boutons d’appeler deux méthodes différentes du même objet.
Je n'ai pas encore vu de méthode aussi souple pour définir un rappel dans une autre langue (je serais très intéressé d'entendre parler d'eux!). L'équivalent le plus proche en C ++ passe probablement par une fonction lambda qui effectue l'appel requis, ce qui restreint à nouveau le code de bibliothèque à un modèle.
C’est cette caractéristique d’Objective-C qui m’a appris à valoriser la capacité d’une langue à transmettre librement tout type d’objets / fonctions / tout concept important que le langage contient, ainsi que le pouvoir de les sauvegarder. variables. Tout point dans un langage qui définit un type de concept, mais ne fournit pas un moyen de le stocker (ou une référence à celui-ci) dans tous les types de variables disponibles, constitue une pierre d'achoppement importante, et probablement une source de beaucoup plus moche, code dupliqué. Malheureusement, les langages de programmation baroques ont tendance à présenter un certain nombre de ces points:
En C ++, vous ne pouvez pas écrire le type d'un VLA, ni y stocker un pointeur. Cela interdit effectivement les véritables tableaux multidimensionnels de taille dynamique (disponibles en C depuis C99).
En C ++, vous ne pouvez pas écrire le type d'un lambda. Vous ne pouvez même pas le taper. Ainsi, il n’ya aucun moyen de contourner un lambda ou de stocker une référence à celui-ci dans un objet. Les fonctions Lambda ne peuvent être transmises qu'à des modèles.
En Fortran, vous ne pouvez pas écrire le type d’une liste de noms. Il n'y a tout simplement aucun moyen de passer une liste de noms à une routine quelconque. Donc, si vous avez un algorithme complexe qui devrait pouvoir gérer deux listes de noms différentes, vous n’avez pas de chance. Vous ne pouvez pas simplement écrire l’algorithme une fois et lui transmettre les listes de noms correspondantes.
Ce ne sont là que quelques exemples, mais vous voyez le point commun: chaque fois que vous voyez une telle restriction pour la première fois, vous ne vous en souciez généralement pas, car cela semble une telle idée folle de faire ce qui est interdit. Cependant, lorsque vous réalisez une programmation sérieuse dans cette langue, vous finissez par en arriver au point où cette restriction précise devient une véritable nuisance.
la source
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!)
Ce que vous venez de décrire ressemble exactement à la manière dont le code d'interface utilisateur piloté par les événements fonctionne dans Delphi. (Et dans .NET WinForms, qui a été fortement influencé par Delphi.)std::vector
. Bien que cela soit un peu moins efficace en raison de la non utilisation de l'allocation de pile, il est fonctionnellement isomorphe à un VLA, donc ne compte pas vraiment comme un problème de type "blub": les programmeurs C ++ peuvent voir comment cela fonctionne et dire: "ah oui C le fait plus efficacement que C ++ ".std::function
.object::method
et le convertir en instance. quelle que soit l'interface attendue par le code de réception. C # a des délégués. Tous les langages fonctionnels avec des objets ont cette fonctionnalité, car c’est fondamentalement le point de section des deux paradigmes.