Le «paradoxe du blub» et c ++


37

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.



2
Je suis d'accord. Tant que cela ne se transformera pas en un débat C ++ vs Lisp, je pense qu'il y a quelque chose à apprendre ici.
Jeffythedragonslayer

@MasonWheeler: 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.
Robert Harvey

2
@RobertHarvey: "Toutes les langues ont la même puissance dans le sens où elles sont équivalentes à Turing, mais ce n'est pas le sens du mot qui préoccupe les programmeurs. (Personne ne veut programmer une machine de Turing.) formellement définissable, mais une façon de l'expliquer serait de dire qu'il fait référence à des fonctionnalités que vous ne pouvez obtenir que dans le langage le moins puissant en écrivant un interprète pour le langage le plus puissant qu'il contient. " - Paul Graham, dans une note de bas de page relative au trollpost en question. (Vous voyez ce que je veux dire?)
Mason Wheeler

@Mason Wheeler: (pas vraiment.)
Robert Harvey

Réponses:


16

Eh bien, puisque vous avez mentionné Haskell:

  1. 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.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
  2. 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.

  3. 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.

  4. Évaluation paresseuse. Encore une fois, il s’agit de penser à un niveau supérieur et de laisser le système gérer l’évaluation.

  5. 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.


2
Une note sur la correspondance des modèles: je ne dirais pas que c'est plus facile à écrire en général, mais après avoir lu un peu sur le problème d'expression, il devient clair que des choses comme les instructions if et switch, les énumérations et le modèle d'observateur sont toutes des implémentations inférieures de Types de données algébriques + Correspondance de modèle. (Et ne
commençons

Ce que vous dites est vrai, mais le problème de l’expression concerne les limitations des types de données algébriques (et la double limitation de la POO standard).
Blaisorblade

@hugomg Voulez-vous dire le modèle de visiteur au lieu de Observer?
Sebastian Redl

Oui. Je change toujours ces deux noms :)
Hugomg

@hugomg Il ne s'agit pas d'avoir Maybe(pour C ++, voir std::optional), mais d'avoir à marquer explicitement les choses comme optionnel / nullable / peut-être.
Déduplicateur

7

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:

auto memoize(alias Fn, T...)(T args) {
    auto key = tuple(args);                               //Key is all the args
    static typeof(Fn(args))[typeof(key)] cache;           //Hashtable!
    return key in cache ? cache[key] : (cache[key] = Fn(args));
}

Tout ce que vous devez faire pour l'appeler est quelque chose comme:

int fib(int n) { return n > 1 ? memoize!(fib)(n - 1) + memoize!(fib)(n - 2) : 1;}
fib(60);

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):

(define (memoize f)
    (let ((table (list)))
        (lambda args
            (cdr
                (or (assoc args table)
                    (let ((entry (cons args (apply f args))))
                        (set! table (cons entry table))
                        entry))))))
(define (fib n)
        (if (<= n 1)
            1
            (+ (fib (1- n))
                (fib (- n 2)))))))
(set! fib (memoize fib))

1
Vous aimez donc APL, où vous pouvez écrire n'importe quoi sur une seule ligne? La taille n'a pas d'importance!
Bo Persson

@Bo: Je n'ai pas utilisé APL. Je ne suis pas sûr de ce que vous entendez par "la taille n'a pas d'importance", mais y a-t-il quelque chose qui ne va pas dans mon code qui vous fait dire cela? Et y a-t-il un avantage à ce que vous fassiez dans un langage différent (comme le C ++) dont je ne suis pas au courant? (J'ai édité un peu les noms de variables, au cas où ce soit de cela que vous parliez.)
Mehrdad

1
@Mehrdad - Mon commentaire concerne le programme le plus compact n'est pas le signe du meilleur langage de programmation. Dans ce cas, APL gagnerait haut la main, car vous faites la plupart des tâches avec un seul opérateur de caractère. Le seul problème est que c'est illisible.
Bo Persson

@Bo: Comme je l'ai dit, je ne recommandais pas APL; Je ne l'ai même jamais vu. La taille était un critère (bien que significatif, comme on peut le voir si vous essayez cela avec C ++) ... mais y at-il un problème avec ce code?
Mehrdad

1
@Matt: Votre code a mémoisé une fonction, mais ce code peut mémoriser n'importe quelle fonction. Ce ne sont pas vraiment équivalents du tout. Si vous essayez réellement d’écrire une fonction de niveau supérieur comme celle-ci dans C ++ 0x, c’est beaucoup plus fastidieux que dans D (bien que ce soit tout à fait possible ... bien que ce ne soit pas possible dans C ++ 03).
Mehrdad

5

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 yieldlangage 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 ++.


Je ne peux même pas commencer à penser combien il serait difficile de créer des générateurs en C ++ étant donné C ++ 2003. C ++ 2011 rendrait les choses plus faciles, mais non triviales. Travaillant régulièrement avec C ++, C # et Python, les générateurs sont facilement la fonctionnalité qui me manque le plus en C ++ (maintenant que C ++ 2011 a ajouté lambdas).

Je sais que je vais me faire tirer dessus, mais si je devais absolument implémenter des générateurs en C ++, je devrais utiliser ... setjmpet longjmp. 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.
Mike DeSimone

@ Mike DeSimone, pouvez-vous préciser (brièvement) comment vous pourriez tenter une solution avec setjmp et longjmp?

1
Les coroutines sont isomorphes aux foncteurs.
GManNickG

1
@Xeo, @ Mike: xkcd.com/292
Mehrdad

5

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 ++.


Les Coroutines sont disponibles dans FreeRTOS ( voir ici ), qui est implémenté en C. Je me demande ce qu'il faudrait pour les faire fonctionner en C ++?
Mike DeSimone

Les co-routines sont un hack méchant pour émuler des objets en C. En C ++, les objets sont utilisés pour regrouper du code et des données. Mais en C, vous ne pouvez pas. Vous utilisez donc la pile de la co-routine pour stocker les données et la fonction de la co-routine pour contenir le code.
MSalters


1
@Ferruccio: Merci pour le lien ... il y en a aussi quelques-uns dans l'article de Wikipedia. @MSalters: qu'est-ce qui vous fait décrire les routines comme un "bidouillage méchant"? Cela me semble une perspective très arbitraire. L'utilisation d'une pile pour stocker l'état est également effectuée par des algorithmes récursifs - est-ce qu'ils sont aussi hackeux? FWIW, Coroutines et OOP sont arrivés sur les lieux à peu près au même moment (début des années 1960) ... dire que le premier est un hack pour des objets en C semble bizarre ... J'imagine que peu de programmeurs en C étaient alors intéressés à émuler des objets, > 15 ans avant C ++.
Tony le

4

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.


Quelle était la différence de vitesse?
quant_dev

1
@quant_dev: un multiple de zillion, bien sûr!
Matt Ellen

Je ne l'ai jamais vraiment mesuré, mais j'avais le sentiment que les grands O étaient différents. A l'origine, j'avais écrit la version C ++ dans un style fonctionnel et cela posait des problèmes de vitesse jusqu'à ce que je lui enseigne à modifier les structures de données au lieu d'en créer de nouvelles, modifiées. Mais cela a également rendu le code plus difficile à lire ...
jeffythedragonslayer

2

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 caret cdrprendre 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?


Vous trouvez que ce choix est une épée à double tranchant. Nous le recherchons tous, mais il y a un côté sombre et cela nous rend malheureux. Il est préférable d'avoir une vision très définie du monde et des limites précises dans lesquelles vous pouvez opérer. Les gens sont des créatures très créatives, capables de faire de grandes choses avec des outils limités. Donc pour résumer, je dis que ce n’est pas le langage de programmation, mais des personnes talentueuses qui peuvent faire chanter n’importe quelle langue!
Tchad

"Suggérer, c'est créer; définir, c'est détruire." Penser que vous pourriez rendre la vie plus facile avec une autre langue vous fait du bien, mais une fois que vous faites le saut, vous devez faire face aux verrues de la nouvelle langue.
Mike DeSimone

2
Je dirais que l'anglais est bien plus puissant et expressif que n'importe quel langage de programmation. Pourtant, les limites de son utilité s'élargissent de jour en jour et son utilité est immense. Une partie de la puissance et de l'expressivité provient de la capacité de communiquer au niveau d'abstraction approprié et de la capacité d'inventer de nouveaux niveaux d'abstraction à l'état incorporé.
molbdnilo

@ Mike alors vous devez traiter avec la communication avec la langue précédente dans la nouvelle;)
Tchad

2

Tableaux associatifs

Un moyen typique de traiter les données est:

  • lire l'entrée et en construire une structure hiérarchique,
  • créer des index pour cette structure (par exemple, un ordre différent),
  • en créant des extraits (parties filtrées),
  • trouver une valeur ou un groupe de valeur (nœud),
  • réorganiser la structure (supprimer des nœuds, ajouter, ajouter, supprimer des sous-éléments en fonction d'une règle, etc.),
  • Parcourez l’arbre et imprimez ou enregistrez certaines parties.

Le bon outil est le tableau associatif .

  • Le meilleur support linguistique pour les tableaux associatifs que j'ai vus est MUMPS , où les tableaux associatifs sont: 1. toujours triés 2. ils peuvent être créés sur disque (appelée base de données), avec la même syntaxe. (Effet secondaire: c'est extrêmement puissant comme base de données, le programmeur a accès au btree natif. Le meilleur système NoSQL de tous les temps.)
  • Mon deuxième prix va à PHP , j'aime foreach et sa syntaxe simple, comme $ a [] = x ou $ a [x] [y] [z] ++ .

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.


1
La divulgation complète: MUMPS n'est pas pour tout le monde. Citation: Pour donner un exemple un peu plus concret de l’horreur qu'est MUMPS, commencez par prendre une partie du Concours international de code C obscurci, une pincée de Perl, deux énormes mesures de FORTRAN et SNOBOL, ainsi que les contributions indépendantes et non coordonnées de des dizaines de chercheurs en médecine, et voilà.
Mike DeSimone

1
En Python, vous pouvez utiliser le type intégré dict(par exemple x = {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] = 8car le langage doit regarder vers l’avenir pour voir si vous allez définir une valeur ou créer un autre niveau; l'expression a[x][y]en elle-même ne vous dit pas.
Mike DeSimone

MUMPS est à l’origine un langage de type Basic avec des tableaux associatifs (peut être directement stocké sur le disque!). Les versions ultérieures contiennent des extensions de procédure, ce qui le rend très similaire au noyau PHP. Les personnes craignant les bases et PHP devraient trouver les oreillons effrayantes, mais pas les autres. Les programmeurs ne le font pas. Et rappelez-vous, c'est un très vieux système, toutes choses étranges, comme des instructions à une lettre (vous pouvez utiliser des noms complets), l'ordre d'évaluation des LR, etc. - ainsi que des solutions non étranges - n'ont qu'un seul objectif: l' optimisation .
ern0

"Nous devrions oublier les petites économies, disons environ 97% du temps: l'optimisation prématurée est la racine de tous les maux. Cependant, nous ne devrions pas laisser passer nos opportunités dans ces 3% critiques." - Donald Knuth. Ce que vous décrivez me semble être un langage traditionnel dont l’accent est mis sur la compatibilité ascendante, et c’est bien. Personnellement, dans ces applications, je considère la maintenabilité plus importante que l'optimisation, et un langage avec des expressions non algébriques et des commandes d'une lettre semble contre-productif. Je sers le client, pas la langue.
Mike DeSimone

@ Mike: liens très divertissants que vous avez postés, j'ai bien rigolé en les lisant.
shuttle87

2

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.


2

Existe-t-il une langue ou un langage puissant que vous utilisez dans un langage qui serait difficile à conceptualiser ou à mettre en œuvre si vous écriviez uniquement en c ++?

Y a-t-il des concepts ou des techniques utiles que vous avez rencontrés dans d'autres langues et qu'il vous aurait été difficile de conceptualiser si vous aviez écrit ou "réfléchi" en c ++?

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 EVALpour 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 #.


1

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.


Notez bien: la plupart de mes programmes sont en C ++. J'aime la langue.
David Hammen

Je pensais que la proposition de gammes était censée résoudre celle-ci? (Même pas en C ++ 17 je pense)
Martin Ba

2
"Mouvements massifs vers la modernité": Quelles fonctionnalités "modernes" C ++ 11 offre-t-il et qui ont été inventées au cours du millénaire actuel?
Giorgio

@MartinBa - D'après ce que je comprends de la proposition "gammes", il s'agit d'un remplacement pour des itérateurs plus faciles à utiliser et moins sujets aux erreurs. Je n'ai vu aucune suggestion selon laquelle ils permettraient quoi que ce soit d'aussi intéressant que la compréhension de liste.
Jules

2
@Giorgio - Quelles sont les caractéristiques d' un langage actuellement populaire qui ont été inventées au cours du présent millénaire?
Jules

0

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.


1
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.)
Mason Wheeler

2
"En C ++, vous ne pouvez pas écrire le type d'un VLA" [...] - en C ++, les VLA de type C99 sont inutiles, car nous en avons 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 ++ ".
Jules

2
"En C ++, vous ne pouvez pas écrire le type d'un lambda. Vous ne pouvez même pas le définir. Ainsi, il n'y a aucun moyen de contourner un lambda ou de stocker une référence dans un objet" - c'est ce que nous proposons std::function.
Jules

3
"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!)." - en Java, vous pouvez écrire object::methodet 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.
Jules

@Jules Vos arguments sont précisément l’objet de Blub-Paradox: En tant que programmeur expérimenté en C ++, vous ne les considérez pas comme des limitations. Cependant, ce sont des limitations, et d'autres langages comme C99 sont plus puissants sur ces points spécifiques. Votre dernier point: il existe des solutions de contournement possibles dans de nombreuses langues, mais je ne connais pas de solution permettant de transmettre le nom d’une méthode à une autre classe et de l’appeler également sur un objet que vous fournissez.
cmaster
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.