En même temps, le switch sur les chaînes de caractères est une fonctionnalité assez douteuse. Les chaînes de caractères viennent à priori toutes de l'utilisateur (ou de l'environnement), donc il faut être robuste à différent détails et il est rare qu'on puisse tester exactement l'égalité avec quelque chose (on voudra plutôt tolérer certains changements, les différences majuscules/minuscules par exemple, les espacements...).
Pour chaque cas de "switch" qui entre dans ce cadre (on peut switcher si on choisit une méthode de normalisation de l'entrée et qu'on compare à des formes normales), il y a sans doute une tripotée de cas où le switch sur les chaînes est utilisé à tort à la place de fonctionnalités plus robustes (aux fautes de frappe dans le code etc.), comme les énumérations, les symboles, ou un type algébrique.
(Mais oui Ada n'est pas la panacée; par exemple la généricité par spécialisation est très lourde à utiliser, on aurait envie d'une forme plus simple de polymorphisme paramétrique.)
Le switch (sur les valeurs) de Go est le "switch done right" (pour peu qu'on se contente d'une fonctionnalité si faible alors qu'on peut avoir des filtrages de motifs nettement plus sympathiques): switch statements in Go.
Ce n'est pas un problème de portée lexicale mais de distinction valeur/référence sur les variables. Pour moi il y a problème de portée quand tu voudrais définir une nouvelle variable localement et que tu ne peux pas, ou que tu voudrais accéder à une variable définie plus globalement, mais que tu ne peux pas ou que ce n'est pas "la bonne" variable qui est choisie.
Il y a bien un problème de ce type en Python (PEP 3104) mais il a été géré de manière raisonnable, c'est pour ça que je n'ai pas mentionné. Cela vient de la distinction C'est moche, mais il y a maintenant un mot clé nonlocal comme rustine pour éviter ça. Ça pose parfois problème pour faire évoluer le langage, comme dans la PEP 3150.
Ben ouais, mais la on parle pas d'un in memory cache avec 16Go de heap ou d'un tomcat qui sert 2000 requêtes/seconde, on parle d'un telephone.
Ce n'est pas cohérent avec ce que tu disais dans ton message précédent:
L'autre core est la pour une raison, et c'est pas pour le gc. Mes 3 applis sur le store font un usage intensif de GCD et tournent avec un pool de 5 a 15 threads. Ces threads, si je peux les faire tourner sur un deuxieme core et gagner du temps sur mon background processing, ben ca fait ca de gagne pour mes utilisateurs. Quand l'appli demarre et qu'elle doit parser 4Mo de json, si je peux saturer un coeur, ca me fait une demi seconde de gagnee, et ca fait une reelle difference a l'usage.
Donc tu as bien parfois des besoins de performances sur ton téléphone. Attention, je n'ai pas dit que tu avais toujours besoin de perfs, au contraire. J'ai dit que je comprends ton insistance sur la fluidité/réactivité et donc le risque lié aux pauses, et que je pense que ça justifie une diminution du throughput global. Par contre tu ne peux pas dire à la fois "on ne veut pas de GC pour éviter les pauses" et d'un autre côté "mais les performances c'est essentiel donc je ne veux pas de GC".
Par ailleurs il ne faut pas trop nous faire pleurer sur les 512 Mo de RAM de ton téléphone, c'est ce qu'on avait il y a quelques années seulement sur les ordinateurs desktop, et on raisonnait déjà sur ces choses là. Le GC OCaml par exemple a été développé début 90, pour être compétitif sur les machines de l'époque.
Pour moi JHC est essentiellement du vaporware pour l'instant; par ailleurs il y a déjà eu pas mal d'essais d'utilisation des régions à grande échelle, pour des résultats souvent mitigés -- le travail sur Cyclone était très bon, mais ça restait très compliqué. J'avais lu une rétrospective sur l'inférence de régions pour SML, mais je ne la retrouve plus.
Les langages synchrones (tu vas vexer des gens en caractérisant Lustre et cie. de "FRP", ces langages reposent sur des concepts qui existaient bien avant la mode récente de la FRP à la sauce Haskell) sont effectivement un cas intéressant où on peut combiner pureté et utilisation très contrôlée de la mémoire, mais je ne les vois pas trop comme des langages généralistes, plutôt comme des langages spécifiques à certains domaines (importants).
Ce que je voulais dire, c'est que si on écrit un algorithme récursif dans un langage avec GC, et qu'on essaie d'y ajouter des malloc/free pour obtenir un algorithme en C par exemple, on se retrouve souvent à vouloir ajouter un freeaprès un appel terminal.
Par exemple (le premier qui me vient en tête, pas forcément le meilleur) :
let rec tri_fusion liste suffixe = match liste with
| [] | [_] -> liste @ suffixe
| _ ->
let gauche, droite = decoupe liste in
tri_fusion gauche (tri_fusion droite suffixe)
Si tu dois insérer des malloc/free, la façon la plus naturelle est la suivante :
let rec tri_fusion liste suffixe = match liste with
| [] | [_] -> liste @ suffixe
| _ ->
let gauche, droite = decoupe liste in
let result = tri_fusion gauche (tri_fusion droite suffixe) in
free_list gauche;
result
(On libère seulement gauche car droite est un suffixe de la liste d'entrée, donc n'es pas possédée par le code appelant).
Si tu veux récupérer un appel terminal, il faut faire une transformation de 'passage de continuation' à la main dans le cas particulier du contexte mémoire: à l'appel récursif, mettre gauche dans une pile de "trucs à libérer après avoir calculé le résultat final":
let rec tri_fusion liste suffixe cadavres = match liste with
| [] | [_] ->
List.iter free_list cadavres;
free_list cadavres;
liste @ suffixe
| _ ->
let gauche, droite = decoupe liste in
tri_fusion gauche (tri_fusion droite suffixe) (gauche :: cadavres)
Ça ne se voit pas dans les langages à GC parce que les actions de libération, qui sont bien effectuées après le retour de la fonction, ne sont pas mémorisées dans la pile d'appel mais redécouvertes a posteriori par le GC quand il parcourt l'espace mémoire; ça permet donc d'avoir plus d'appels terminaux.
(Et puis, y'a-tu vraiment des gens qui bossent sur la FP sans GC ? T'as des références récentes ? Je suis curieux…)
ATS est un excellent exemple que j'invite fortement les gens intéressés par la programmation bas niveau et statiquement sûre à regarder. Pour faire de la programmation fonctionnelle sans GC (ou au moins en pouvant décider localement de ne pas l'utiliser), il faut presque nécessairement un système de types linéaires, donc tu peux regarder plus largement dans la littérature de recherche sur les langages à types linéaires -- même si tous ne sont pas orientés contrôle bas-niveau de la mémoire.
Enfin le fait d'avoir des fonctions de première classes (qui peuvent être utilisées comme n'importe quelle valeur) est très utile pour avoir un style fonctionnel sans contraintes, et justement C n'a pas cette fonctionnalité: tu as les pointeurs de fonctions, mais tu ne peux pas pour autant définir des fonctions imbriquées les unes dans les autres, ou plus généralement construire des fonctions dynamiquement au runtime -- on émule ça en faisant de la conversion de clôture à la main pour se ramener à avoir seulement des fonctions toplevel, mais ça revient à demander au programmeur de faire à la main une passe de compilation.
Par ailleurs, le fait d'avoir un GC est quand même très utile pour la plupart des idiomes de la programmation fonctionnelle (par exemple, sans GC, beaucoup de fonctions ne sont plus naturellement tail-récursives), c'est un style qui alloue beaucoup et dans lequel la durée de vie des valeurs est difficile à maîtriser statiquement. On peut faire de la programmation fonctionnelle sans GC, mais c'est subtil et ça reste plutôt un sujet de recherche.
Un avantage d'avoir une séparation entre implémentation et interface est que ça permet de donner plusieurs interfaces à une même implémentation. Par exemple je peux avoir un fichier M qui est compilé avec une interface S1, utilisée par quelques modules proches qui sont dans le même domaine applicatif et qui connaissent les entrailles de M, et une interface S2 plus restrictive pour l'usage "public" par le reste des composants du projet, dont tu ne veux pas qu'ils dépendent de détails de M qui pourraient changer dans le futur.
C'est un peu l'équivalent de ce qu'on fait avec les "friend methods" dans certains langages objets, sauf que là tu peux avoir la granularité que tu veux (pas d'interface (= tout public), une interface pour tout le monde, plusieurs niveaux d'interfaces...).
Ça n'impose pas forcément de distinguer interface et implémentation, certains langages peuvent (dans une implémentation) "celer" des modules avec une signature plus restrictive, et donc on peut obtenir la même chose avec seulement des implémentations, mais c'est une fonctionnalité plus avancée et moins explicite.
Seulement, pour peu qu'on aie à travailler avec des types différents (par exemple, de taille différente), se retrouver avec des +, +., +!, ++, +/, etc. n'est pas ce qu'il se fait de plus commode et de plus lisible.
Depuis OCaml 3.12 (et avant avec des extensions syntaxiques) on peut faire des "open" de modules dans un contexte local. Avec (encore une fois) du bon support du côté des bibliothèques, tu peux écrire:
open Batteries
let rec fibo n = Big_int.(match n with
| 0 -> of_int 1
| n -> fibo Int.(n - 1) + fibo Int.(n - 2)
)
(Et tu as fibo : int -> Big_int.t)
Du coup au lieu d'utiliser des opérateurs différents, tu utilises la construction Module.(expression) pour préciser le contexte local. C'est un peu plus lourd, mais c'est agréable à utiliser en pratique car plus explicite. Ça ne vaut pas des type-classes bien sûr, mais là il s'agit d'une fonctionnalité syntaxique simple et pas d'un concept complexe à ajouter au langage.
Pour en avoir fait un peu, c'est assez mal documenté et nécessite de repasser toutes les fonctions à la main en convertissant chaque argument avec les macros qui vont bien.
Tu as raison pour les bindings C, c'est pas facile, mais ça c'est la vie, pour peu qu'un langage s'éloigne un peu du support runtime C c'est vite difficile de faire des ponts (cf. ce billet qui compare les FFI de différents langages). On pourrait vouloir des techniques de bindings mieux vérifiées statiquement et donc moins à la merci des erreurs humaines, mais c'est vite un peu compliqué. Cf. le travail de l'excellent langage ATS.
Après, on peut rester minimal sans forcément utiliser des tas de symboles. Par exemple, Python fait ça très bien, tout en étant différent du C.
Je ne vois pas trop en quoi la grammaire Python serait plus "minimale" que celle de OCaml. À vue de nez elles sont du même ordre de grandeur de complexité. Il y a la question de l'indentation significative ou pas, c'est un sujet de troll, ce qui est vrai c'est que ça évite d'écrire explictement certains délimiteurs (begin/end et in/;), mais à part ça je ne vois pas trop la différence.
Aussi, lorsque tu écris que l'on peut mélanger la programmation fonctionnelle et impérative avec des langages fonctionnels, je comprend utiliser des constructions impératives pour exprimer certaines choses [...]. Et là dessus oui je suis d'accord.
Donc je ne comprends pas trop sur quoi on n'est pas d'accord. Pour moi avoir une "approche" fonctionnelle c'est d'utiliser surtout, globalement, des constructions fonctionnelles, et avoir une "approche" impérative c'est d'utiliser surtout, globalement, des constructions impératives (ou, dans certains cas, trouver des façons astucieuses de faire au premier ordre mais sans trop se fatiguer des choses qu'on ferait naturellement avec des fonctions d'ordre supérieur; par exemple un bon programmeur C peut aller très très loin avec juste des tableaux et des boucles).
Si tu as beaucoup d'effets de bord observables à moyenne/grande échelle dans ton application, tu vas devoir (pour faire ce que tu veux) utiliser surtout des constructions impératives. Si les effets de bord ne sont qu'aux frontières d'interaction, ou alors utilisées localement et pas observables de l'extérieur, on utilise surtout des constructions fonctionnelles. Il me semble donc que la "façon de penser" est fortement correllée avec les constructions utilisées. Sur un choix de design donné (par exemple passage d'état explicite ou par effet de bord), on fait un choix selon son habitude, celles des autres développeurs, et le compromis demandé entre rigide et implicite d'un côté (effets de bords, style monadique compris) et flexible et explicite de l'autre (style fonctionnel).
Bref, moi je peux lire du code et dire localement quel style il utilise : c'est concret, c'est (relativement) objectif. Par contre les grands discours sur "l'approche", la "façon de penser", "l'esprit" des programmes ou du programmeur, hum, bof.
Quant à mon expression de "déclaratif", il signifie ni plus ni moins que la manière dont sont écrites les fonctions : en déclarant ce qu'elles font par le biais d'autres fonctions (à opposer à stipuler comment elles doivent le faire).
À ce compte-là, C est un langage "déclaratif" parce que quand tu écris une fonction tu "déclares" ce qu'elle doit faire.
Mais dans ce cas, je peux aussi dire qu'aucun langage n'est "compilé" selon ta définition, puisque le jeu d'instruction x86 est une machine virtuelle dont les opcodes sont reconverties au vol par le processeur vers des sets de microcode RISC-style plus efficaces, en tout cas sur toutes les architectures récentes (eg. Sandy Bridge).
Ou alors les téléphones portables qui gèrent le bytecode Java au niveau matériel (cf. Jazelle), ça fait de Java (ou Scala, Clojure...) un langage compilé selon ta définition ?
Consommer moins de mémoire c'est bien, mais de là qualifier mon reproche de "FUD" je pense que tu vas un peu loin. Les new-style classes garantissent que le nombre de membres des instances d'une classe ne change pas; c'est bien, mais ça ne suffit pas : la preuve, CPython ne va toujours pas follement plus vite qu'avant, et est bien plus lent qu'une implémentation codée avec efforts équivalents¹ pour un langage plus sobre, LuaJIT.
Si tu veux un truc qui ressemble à du python et qui est vraiment implémentable rapidement, il vaut mieux parler de RPython à mon humble avis.
¹: oui c'est important de prendre en compte l'effort qui va dans les implémentations; on ne peut pas vraiment comparer l'état de Pypy et celui des machines virtuelles Javascript par exemple, parce que dans le second cas des dizaines d'années de travail ont été investies au total, pour rendre rapide des implémentations d'un langage très difficile à implémenter rapidement.
Posté par gasche .
En réponse au journal Votre langage idéal ?.
Évalué à 3.
Dernière modification le 30 janvier 2012 à 12:47.
Les patterns sont linéaires donc fac acc i n n = ... ne va pas marcher, il faut faire un test d'égalité à la main. Donc ouais, on peut écrire ça comme ça, mais c'est plus compliqué donc sans grand intérêt. À choisir je préfère écrire une boucle for qui descend, si tu veux une correspondance plus forte.
int fac(int n)
{
int acc = 1;
for (int i = n; i > 0; --i)
acc *= i;
return acc;
}
Posté par gasche .
En réponse au journal Votre langage idéal ?.
Évalué à 3.
Dernière modification le 30 janvier 2012 à 11:53.
Pas du tout, OCaml est un langage visionnaire qui a prévu il y a des années que le futur, c'était langages single-threadé. 20 ans après, Javascript s'y (re)met enfin. Qui va suivre ? L'idée de runtimes indépendants communiquant par passage de messages a de l'avenir...
Even non-Mozilla SpiderMonkey embeddings had reportedly experienced problems that pushed them toward a similar shared-nothing design. Thus, there was little reason to maintain the non-trivial complexity caused by multi-threading support.
À ma connaissance CL n'a pas de système de typage digne de ce nom (contrairement à Typed Racket (ex. Typed Scheme) par exemple, donc l'espoir est possible). Après ça dépend de ce qu'on met dans "digne de ce nom", ces temps-ci la moyenne est plutôt à la baisse (Go, Dart...) (enfin non, Rust).
Posté par gasche .
En réponse au journal Votre langage idéal ?.
Évalué à 5.
Dernière modification le 30 janvier 2012 à 11:26.
Non, je ne pense pas. Pour moi une implémentation qui compile à la volée est un compilateur (... à la volée). Note le choix du mot "implémentation" et pas "langage"; un langage n'est pas forcément défini par une implémentation unique, ça n'a pas de sens par exemple de dire que C est langage "interprété" parce qu'il existe des interpréteurs de C.
Littéralement un interpréteur est une implémentation qui exécute le programme directement en parcourant le code source, sans aucune étape de transformation intermédiaire. En pratique il y a toujours une phase de parsing voire de simplification et tu as donc une définition molle de "travaille sur une donnée qui a conservé la structure du programme initial". Toute phase de transformation globale qui change sa structure (pour la simplifier en général) est une forme de compilation.
Le terme "interpréter" a encore du sens quand on fait des sémantiques formelles de langages de programmation théoriques, où certaines sémantiques (sémantiques opérationnelles) sont clairement des "interprétations", et d'autres (exécution sur machines virtuelles, ou même certaines sémantiques dénotationnelles) des "compilations". Pour ce qui est des langages réels raisonnables, il faut l'éviter comme la peste car il est entouré de trop de bêtises.
Je suis bien conscient et je n'ai pas dit qu'on s'en moquait, loin de là. Mais je pense qu'il faut assumer un peu. Quand on lit le message ci-dessus on dirait que les développeurs Python/whatever lui mettent un flingue sur la tempe pour le forcer à changer son code. Ben non, il a codé contre une version donnée du langage, il peut continuer à maintenir l'environnement de programmation qu'il avait, sans changer une seule ligne. S'il doit changer c'est parce qu'il veut utiliser du nouveau, et là il faut accepter que la nouveauté a aussi des inconvénients.
Je comprends très bien ta critique de l'instabilité de certains langage. Mais c'est quelque chose dont les gens sont au courant, et qu'il faut assumer. Si ta priorité c'est d'écrire du code en production qui tourne pendant des années sans aucune maintenance, tu utilises un langage connu pour être resté stable (ou au moins avoir soigné la compatbilité arrière) dans le passé. Tu codes pas ton truc dans le dernier langage de script à la mode.
Tout ça est une question de compromis faits par des adultes responsables et consentants. On ne va pas me faire pleurer sur le thème "bouh je suis forcé de mettre à jour mon code", c'est pas vrai, personne n'est obligé à quoi que ce soit, les gens font leurs choix.
Distinction à la noix, Python fait de la compilation vers du bytecode depuis des lustres. "Interpréter" ne veut plus rien dire de nos jours, c'est du FUD répandu par des gens qui veulent entretenir une distinction ignorante et subjective entre différents langages de programmation.
Python est lent (ou plutôt, très difficile à rendre rapide) parce que c'est un langage dynamique dont les constructions de base permettent de faire tout et n'importe quoi sans garantie sur le code (enfin avec une certaine retenue, c'est moins délirant que le monkey-patching qu'adorent les rubystes dégénérés), pas parce qu'il serait "interprété" plutôt que "compilé".
Il y a aussi la possibilité de faire des vérifications sémantiques au niveau du bytecode. Si tu veux permettre à tes utilisateurs d'exécuter du code arbitraire venant de distributeurs tiers que tu ne contrôles pas, c'est quand même assez rassurant (même si ça ne suffit pas complètement), tu peux au moins préserver l'intégrité du système.
Le choix d'Apple c'est de prendre comme brique de base un langage non-sûr et donc difficile à sécuriser, et de mettre un rideau de fer entre les développeurs et l'utilisateur, en mettant en place un point centralisé de contrôle et de validation du code. Comme ça en plus des problématiques de sécurité ils peuvent forcer une consistence de l'interface, mais aussi éliminer des concurrents gênants et se laisser le monopole sur certaines applications. C'est gagnant pour eux et, il faut l'avouer, confortable pour l'utilisateur... tant qu'il n'essaie pas trop d'être indépendant, mais Apple sait se trouver un public de gens qui privilégient leur confort (comment ne pas écrire petit bourgeois en continuation naturelle de ma phrase ? ;)
Tant pis pour les développeurs qui veulent un peu de flexibilité, pour les applications qui veulent encourager le partage de code entre utilisateurs, les gens qui veulent apprendre à programmer et montrer leur appli à leurs amis sans payer la taxe... C'est un autre compromis, mais à choisir Java et un mobile qui chauffe ça ne me semble pas si mal.
On pourrait avoir le beurre et l'argent du beurre avec des outils de vérification statique sur des langages plus bas-niveau que Java, un bon sandboxing par défaut (par exemple NaCL et autres projets de LLVM aseptisé), et des modèles de sécurité robustes mais flexibles pour gérer le transfert de droit dans un contexte moins restrictif -- ce que permettraient la sécurité par capabilities par exemple, comme poussé par certains au sein de Googlet et ailleurs.
Personne ne t'oblige à le modifier. Tu gardes la même version du compilateur et voilà. Tu dois le modifier si tu comptes continuer à le compiler avec des versions suivantes du langages, ce que tu fais si tu veux profiter des nouvelles fonctionnalités ajoutées depuis son écriture (ou le combiner avec des projets qui le font).
C'est donnant-donnant, si tu veux profiter des nouvelles fonctionnalités ajoutées par les développeurs, tu acceptes aussi de prendre en compte les modifications qu'ils ont effectuées pour améliorer la qualité du langage ou se simplifier la vie.
Si tu refuses ce choix, pas de problème, (si l'implémentation est libre) le compilateur avec la version qui t'intéresse tu l'as, tu le gardes en prod depuis des années, et tu pourras continuer à l'utiliser tant que tu veux... et que tu ne changes pas ton environnement, et ouais, mais pourquoi changer un environnement qui marche en prod depuis des années de toute façon ?
Des variables à portée lexicale (comme les locales en Lua)
Quoi, c'est une fonctionnalité ça ? Je ne dois pas comprendre parce qu'il me semble que c'est la base de la base. Je dirais plutôt que les langages qui ne l'ont pas (ou qui l'amputent d'une façon ou d'une autre) craignent un max (oui, dommage que ça incluse des langages aussi populaires que Coffeescript, ou les premières versions de Ruby).
Note que toi tu as besoin d'un autre "concept": la récursion terminale
Je n'ai pas besoin de ce "concept" pour me convaincre que ces fonctions font ce que je veux. Par ailleurs le fait qu'un appel est terminal ou non peut être vérifié par un critère syntaxique simple. Dans tous les cas son usage ne rend certainement pas le code "moins lisible".
Ta focalisation sur la récursion terminale est à mon avis signe du fait que tu te compliques la vie inutilement. Si tu réfléchis à ce que fais un programme fonctionnel en ayant en tête la façon dont c'est compilé vers du langage machine, l'explication de ce qu'est la récursion terminale est un peu technique et cela paraît être un cas particulier spécifique. Par contre si tu raisonnes simplement en terme de "évaluer c'est réduire le programme étapes par étapes vers le résultat", les bonnes propriétés de la récursion terminale viennent tout naturellement (au lieu de raisonner sur la taille de la pile, on raisonne sur la taille du programme réduit), et on n'a pas besoin d'une explication spécifique pour cela. C'est en fait une façon tout à fait valide et simple de penser aux programmes, indépendamment des aspects bas-niveau de la compilation.
Pas de souci pour fibonacci sauf que tu as du reconstruire la fonction exactement comme en code impératif.
Bien sûr, c'est normal, il n'y a rien de magique à écrire du code fonctionnel. Je n'ai jamais dit, moi, qu'une version serait "beaucoup plus lisible" qu'une autre. Mais c'est vrai que les miennes sont beaucoup plus courtes (grâce au choix opportuniste d'un langage à la syntaxe dépouillée), et aussi plus simples grâce à l'abandon du concept de variable modifiable; je n'ai pas besoin de différencier le calcul de la valeur et son retour.
Sinon pour l'évaluation paresseuse, j'ai lu quelques fois des remarques d'utilisateurs qui trouvaient que ça donnait des performances difficiles a maîtriser..
Je suis d'accord et je ne suis pas pour utiliser la paresse par défaut à outrance. J'ai écrit mes exemples en Haskell parce que la légèreté syntaxique permet d'illustrer mon point très efficacement, mais dans la vraie vie j'utilise plutôt OCaml, qui est un langage strict par défaut.
Par ailleurs il y a des interactions intéressantes entre la paresse et la récursivité : en Haskell on ne recherche pas la récursivité terminale, et elle est même néfaste aux performances en général. Je te laisse creuser le sujet si ça t'intéresse, mais dans tous les cas les fonctions écrites ci-dessus sont strictes de toute façon, donc elles ne seront pas évaluées de façon paresseuse (le compilateur s'en rend bien compte).
Pour le fun, une version de fibonacci qui a la bonne complexité et utilise à fond l'évaluation paresseuse (c'est fun et ça fait un bon exercice de gymnastique intellectuelle, mais je ne recommande pas d'écrire comme ça):
fib n = fibs !! n
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
(zipWith (+) envoie la liste des sommes terme-à-terme de deux listes de même taille, et tail renvoie la liste privée de son premier élément. (!!) est l'accès au n-ième élément d'une liste).
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
En même temps, le switch sur les chaînes de caractères est une fonctionnalité assez douteuse. Les chaînes de caractères viennent à priori toutes de l'utilisateur (ou de l'environnement), donc il faut être robuste à différent détails et il est rare qu'on puisse tester exactement l'égalité avec quelque chose (on voudra plutôt tolérer certains changements, les différences majuscules/minuscules par exemple, les espacements...).
Pour chaque cas de "switch" qui entre dans ce cadre (on peut switcher si on choisit une méthode de normalisation de l'entrée et qu'on compare à des formes normales), il y a sans doute une tripotée de cas où le switch sur les chaînes est utilisé à tort à la place de fonctionnalités plus robustes (aux fautes de frappe dans le code etc.), comme les énumérations, les symboles, ou un type algébrique.
(Mais oui Ada n'est pas la panacée; par exemple la généricité par spécialisation est très lourde à utiliser, on aurait envie d'une forme plus simple de polymorphisme paramétrique.)
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Le switch (sur les valeurs) de Go est le "switch done right" (pour peu qu'on se contente d'une fonctionnalité si faible alors qu'on peut avoir des filtrages de motifs nettement plus sympathiques): switch statements in Go.
[^] # Re: L'idéal
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Ce n'est pas un problème de portée lexicale mais de distinction valeur/référence sur les variables. Pour moi il y a problème de portée quand tu voudrais définir une nouvelle variable localement et que tu ne peux pas, ou que tu voudrais accéder à une variable définie plus globalement, mais que tu ne peux pas ou que ce n'est pas "la bonne" variable qui est choisie.
Il y a bien un problème de ce type en Python (PEP 3104) mais il a été géré de manière raisonnable, c'est pour ça que je n'ai pas mentionné. Cela vient de la distinction C'est moche, mais il y a maintenant un mot clé
nonlocal
comme rustine pour éviter ça. Ça pose parfois problème pour faire évoluer le langage, comme dans la PEP 3150.[^] # Re: pour moi
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Ce n'est pas cohérent avec ce que tu disais dans ton message précédent:
Donc tu as bien parfois des besoins de performances sur ton téléphone. Attention, je n'ai pas dit que tu avais toujours besoin de perfs, au contraire. J'ai dit que je comprends ton insistance sur la fluidité/réactivité et donc le risque lié aux pauses, et que je pense que ça justifie une diminution du throughput global. Par contre tu ne peux pas dire à la fois "on ne veut pas de GC pour éviter les pauses" et d'un autre côté "mais les performances c'est essentiel donc je ne veux pas de GC".
Par ailleurs il ne faut pas trop nous faire pleurer sur les 512 Mo de RAM de ton téléphone, c'est ce qu'on avait il y a quelques années seulement sur les ordinateurs desktop, et on raisonnait déjà sur ces choses là. Le GC OCaml par exemple a été développé début 90, pour être compétitif sur les machines de l'époque.
[^] # Re: Programmation Fonctionnelle
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Pour moi JHC est essentiellement du vaporware pour l'instant; par ailleurs il y a déjà eu pas mal d'essais d'utilisation des régions à grande échelle, pour des résultats souvent mitigés -- le travail sur Cyclone était très bon, mais ça restait très compliqué. J'avais lu une rétrospective sur l'inférence de régions pour SML, mais je ne la retrouve plus.
Les langages synchrones (tu vas vexer des gens en caractérisant Lustre et cie. de "FRP", ces langages reposent sur des concepts qui existaient bien avant la mode récente de la FRP à la sauce Haskell) sont effectivement un cas intéressant où on peut combiner pureté et utilisation très contrôlée de la mémoire, mais je ne les vois pas trop comme des langages généralistes, plutôt comme des langages spécifiques à certains domaines (importants).
[^] # Re: Programmation Fonctionnelle
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 4.
Ce que je voulais dire, c'est que si on écrit un algorithme récursif dans un langage avec GC, et qu'on essaie d'y ajouter des
malloc
/free
pour obtenir un algorithme en C par exemple, on se retrouve souvent à vouloir ajouter unfree
après un appel terminal.Par exemple (le premier qui me vient en tête, pas forcément le meilleur) :
Si tu dois insérer des
malloc/free
, la façon la plus naturelle est la suivante :(On libère seulement
gauche
cardroite
est un suffixe de la liste d'entrée, donc n'es pas possédée par le code appelant).Si tu veux récupérer un appel terminal, il faut faire une transformation de 'passage de continuation' à la main dans le cas particulier du contexte mémoire: à l'appel récursif, mettre
gauche
dans une pile de "trucs à libérer après avoir calculé le résultat final":Ça ne se voit pas dans les langages à GC parce que les actions de libération, qui sont bien effectuées après le retour de la fonction, ne sont pas mémorisées dans la pile d'appel mais redécouvertes a posteriori par le GC quand il parcourt l'espace mémoire; ça permet donc d'avoir plus d'appels terminaux.
ATS est un excellent exemple que j'invite fortement les gens intéressés par la programmation bas niveau et statiquement sûre à regarder. Pour faire de la programmation fonctionnelle sans GC (ou au moins en pouvant décider localement de ne pas l'utiliser), il faut presque nécessairement un système de types linéaires, donc tu peux regarder plus largement dans la littérature de recherche sur les langages à types linéaires -- même si tous ne sont pas orientés contrôle bas-niveau de la mémoire.
[^] # Re: Programmation Fonctionnelle
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Enfin le fait d'avoir des fonctions de première classes (qui peuvent être utilisées comme n'importe quelle valeur) est très utile pour avoir un style fonctionnel sans contraintes, et justement C n'a pas cette fonctionnalité: tu as les pointeurs de fonctions, mais tu ne peux pas pour autant définir des fonctions imbriquées les unes dans les autres, ou plus généralement construire des fonctions dynamiquement au runtime -- on émule ça en faisant de la conversion de clôture à la main pour se ramener à avoir seulement des fonctions toplevel, mais ça revient à demander au programmeur de faire à la main une passe de compilation.
Par ailleurs, le fait d'avoir un GC est quand même très utile pour la plupart des idiomes de la programmation fonctionnelle (par exemple, sans GC, beaucoup de fonctions ne sont plus naturellement tail-récursives), c'est un style qui alloue beaucoup et dans lequel la durée de vie des valeurs est difficile à maîtriser statiquement. On peut faire de la programmation fonctionnelle sans GC, mais c'est subtil et ça reste plutôt un sujet de recherche.
[^] # Re: J'aimerais
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Un avantage d'avoir une séparation entre implémentation et interface est que ça permet de donner plusieurs interfaces à une même implémentation. Par exemple je peux avoir un fichier M qui est compilé avec une interface S1, utilisée par quelques modules proches qui sont dans le même domaine applicatif et qui connaissent les entrailles de M, et une interface S2 plus restrictive pour l'usage "public" par le reste des composants du projet, dont tu ne veux pas qu'ils dépendent de détails de M qui pourraient changer dans le futur.
C'est un peu l'équivalent de ce qu'on fait avec les "friend methods" dans certains langages objets, sauf que là tu peux avoir la granularité que tu veux (pas d'interface (= tout public), une interface pour tout le monde, plusieurs niveaux d'interfaces...).
Ça n'impose pas forcément de distinguer interface et implémentation, certains langages peuvent (dans une implémentation) "celer" des modules avec une signature plus restrictive, et donc on peut obtenir la même chose avec seulement des implémentations, mais c'est une fonctionnalité plus avancée et moins explicite.
[^] # Re: J'aimerais
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
"duration:duree options:option", si ça se lit comme une de tes phrases habituelles c'est que tu écris drôlement mal.
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Merci d'avoir bien écrit ce que j'essayais de dire.
[^] # Re: ocaml...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Depuis OCaml 3.12 (et avant avec des extensions syntaxiques) on peut faire des "open" de modules dans un contexte local. Avec (encore une fois) du bon support du côté des bibliothèques, tu peux écrire:
(Et tu as
fibo : int -> Big_int.t
)Du coup au lieu d'utiliser des opérateurs différents, tu utilises la construction
Module.(expression)
pour préciser le contexte local. C'est un peu plus lourd, mais c'est agréable à utiliser en pratique car plus explicite. Ça ne vaut pas des type-classes bien sûr, mais là il s'agit d'une fonctionnalité syntaxique simple et pas d'un concept complexe à ajouter au langage.Tu as raison pour les bindings C, c'est pas facile, mais ça c'est la vie, pour peu qu'un langage s'éloigne un peu du support runtime C c'est vite difficile de faire des ponts (cf. ce billet qui compare les FFI de différents langages). On pourrait vouloir des techniques de bindings mieux vérifiées statiquement et donc moins à la merci des erreurs humaines, mais c'est vite un peu compliqué. Cf. le travail de l'excellent langage ATS.
Je ne vois pas trop en quoi la grammaire Python serait plus "minimale" que celle de OCaml. À vue de nez elles sont du même ordre de grandeur de complexité. Il y a la question de l'indentation significative ou pas, c'est un sujet de troll, ce qui est vrai c'est que ça évite d'écrire explictement certains délimiteurs (begin/end et in/;), mais à part ça je ne vois pas trop la différence.
[^] # Re: Hum ...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
Donc je ne comprends pas trop sur quoi on n'est pas d'accord. Pour moi avoir une "approche" fonctionnelle c'est d'utiliser surtout, globalement, des constructions fonctionnelles, et avoir une "approche" impérative c'est d'utiliser surtout, globalement, des constructions impératives (ou, dans certains cas, trouver des façons astucieuses de faire au premier ordre mais sans trop se fatiguer des choses qu'on ferait naturellement avec des fonctions d'ordre supérieur; par exemple un bon programmeur C peut aller très très loin avec juste des tableaux et des boucles).
Si tu as beaucoup d'effets de bord observables à moyenne/grande échelle dans ton application, tu vas devoir (pour faire ce que tu veux) utiliser surtout des constructions impératives. Si les effets de bord ne sont qu'aux frontières d'interaction, ou alors utilisées localement et pas observables de l'extérieur, on utilise surtout des constructions fonctionnelles. Il me semble donc que la "façon de penser" est fortement correllée avec les constructions utilisées. Sur un choix de design donné (par exemple passage d'état explicite ou par effet de bord), on fait un choix selon son habitude, celles des autres développeurs, et le compromis demandé entre rigide et implicite d'un côté (effets de bords, style monadique compris) et flexible et explicite de l'autre (style fonctionnel).
Bref, moi je peux lire du code et dire localement quel style il utilise : c'est concret, c'est (relativement) objectif. Par contre les grands discours sur "l'approche", la "façon de penser", "l'esprit" des programmes ou du programmeur, hum, bof.
À ce compte-là, C est un langage "déclaratif" parce que quand tu écris une fonction tu "déclares" ce qu'elle doit faire.
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 5.
Mais dans ce cas, je peux aussi dire qu'aucun langage n'est "compilé" selon ta définition, puisque le jeu d'instruction x86 est une machine virtuelle dont les opcodes sont reconverties au vol par le processeur vers des sets de microcode RISC-style plus efficaces, en tout cas sur toutes les architectures récentes (eg. Sandy Bridge).
Ou alors les téléphones portables qui gèrent le bytecode Java au niveau matériel (cf. Jazelle), ça fait de Java (ou Scala, Clojure...) un langage compilé selon ta définition ?
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 4.
Consommer moins de mémoire c'est bien, mais de là qualifier mon reproche de "FUD" je pense que tu vas un peu loin. Les new-style classes garantissent que le nombre de membres des instances d'une classe ne change pas; c'est bien, mais ça ne suffit pas : la preuve, CPython ne va toujours pas follement plus vite qu'avant, et est bien plus lent qu'une implémentation codée avec efforts équivalents¹ pour un langage plus sobre, LuaJIT.
Si tu veux un truc qui ressemble à du python et qui est vraiment implémentable rapidement, il vaut mieux parler de RPython à mon humble avis.
¹: oui c'est important de prendre en compte l'effort qui va dans les implémentations; on ne peut pas vraiment comparer l'état de Pypy et celui des machines virtuelles Javascript par exemple, parce que dans le second cas des dizaines d'années de travail ont été investies au total, pour rendre rapide des implémentations d'un langage très difficile à implémenter rapidement.
[^] # Re: Un langage ontologique et métaphorique
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 4.
Fiouu, à côté Lisaac c'était mainstream !
Enfin c'est bien, on peut sacrément innover en explorant ces choses là.
[^] # Re: Hum ...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3. Dernière modification le 30 janvier 2012 à 12:47.
Les patterns sont linéaires donc
fac acc i n n = ...
ne va pas marcher, il faut faire un test d'égalité à la main. Donc ouais, on peut écrire ça comme ça, mais c'est plus compliqué donc sans grand intérêt. À choisir je préfère écrire une boucle for qui descend, si tu veux une correspondance plus forte.[^] # Re: ocaml...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3. Dernière modification le 30 janvier 2012 à 11:53.
Pas du tout, OCaml est un langage visionnaire qui a prévu il y a des années que le futur, c'était langages single-threadé. 20 ans après, Javascript s'y (re)met enfin. Qui va suivre ? L'idée de runtimes indépendants communiquant par passage de messages a de l'avenir...
[^] # Re: L'idéal
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 2.
À ma connaissance CL n'a pas de système de typage digne de ce nom (contrairement à Typed Racket (ex. Typed Scheme) par exemple, donc l'espoir est possible). Après ça dépend de ce qu'on met dans "digne de ce nom", ces temps-ci la moyenne est plutôt à la baisse (Go, Dart...) (enfin non, Rust).
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 5. Dernière modification le 30 janvier 2012 à 11:26.
Non, je ne pense pas. Pour moi une implémentation qui compile à la volée est un compilateur (... à la volée). Note le choix du mot "implémentation" et pas "langage"; un langage n'est pas forcément défini par une implémentation unique, ça n'a pas de sens par exemple de dire que C est langage "interprété" parce qu'il existe des interpréteurs de C.
Littéralement un interpréteur est une implémentation qui exécute le programme directement en parcourant le code source, sans aucune étape de transformation intermédiaire. En pratique il y a toujours une phase de parsing voire de simplification et tu as donc une définition molle de "travaille sur une donnée qui a conservé la structure du programme initial". Toute phase de transformation globale qui change sa structure (pour la simplifier en général) est une forme de compilation.
Le terme "interpréter" a encore du sens quand on fait des sémantiques formelles de langages de programmation théoriques, où certaines sémantiques (sémantiques opérationnelles) sont clairement des "interprétations", et d'autres (exécution sur machines virtuelles, ou même certaines sémantiques dénotationnelles) des "compilations". Pour ce qui est des langages réels raisonnables, il faut l'éviter comme la peste car il est entouré de trop de bêtises.
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3.
Je suis bien conscient et je n'ai pas dit qu'on s'en moquait, loin de là. Mais je pense qu'il faut assumer un peu. Quand on lit le message ci-dessus on dirait que les développeurs Python/whatever lui mettent un flingue sur la tempe pour le forcer à changer son code. Ben non, il a codé contre une version donnée du langage, il peut continuer à maintenir l'environnement de programmation qu'il avait, sans changer une seule ligne. S'il doit changer c'est parce qu'il veut utiliser du nouveau, et là il faut accepter que la nouveauté a aussi des inconvénients.
Je comprends très bien ta critique de l'instabilité de certains langage. Mais c'est quelque chose dont les gens sont au courant, et qu'il faut assumer. Si ta priorité c'est d'écrire du code en production qui tourne pendant des années sans aucune maintenance, tu utilises un langage connu pour être resté stable (ou au moins avoir soigné la compatbilité arrière) dans le passé. Tu codes pas ton truc dans le dernier langage de script à la mode.
Tout ça est une question de compromis faits par des adultes responsables et consentants. On ne va pas me faire pleurer sur le thème "bouh je suis forcé de mettre à jour mon code", c'est pas vrai, personne n'est obligé à quoi que ce soit, les gens font leurs choix.
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3.
Distinction à la noix, Python fait de la compilation vers du bytecode depuis des lustres. "Interpréter" ne veut plus rien dire de nos jours, c'est du FUD répandu par des gens qui veulent entretenir une distinction ignorante et subjective entre différents langages de programmation.
Python est lent (ou plutôt, très difficile à rendre rapide) parce que c'est un langage dynamique dont les constructions de base permettent de faire tout et n'importe quoi sans garantie sur le code (enfin avec une certaine retenue, c'est moins délirant que le monkey-patching qu'adorent les rubystes dégénérés), pas parce qu'il serait "interprété" plutôt que "compilé".
[^] # Re: pour moi
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 6.
Il y a aussi la possibilité de faire des vérifications sémantiques au niveau du bytecode. Si tu veux permettre à tes utilisateurs d'exécuter du code arbitraire venant de distributeurs tiers que tu ne contrôles pas, c'est quand même assez rassurant (même si ça ne suffit pas complètement), tu peux au moins préserver l'intégrité du système.
Le choix d'Apple c'est de prendre comme brique de base un langage non-sûr et donc difficile à sécuriser, et de mettre un rideau de fer entre les développeurs et l'utilisateur, en mettant en place un point centralisé de contrôle et de validation du code. Comme ça en plus des problématiques de sécurité ils peuvent forcer une consistence de l'interface, mais aussi éliminer des concurrents gênants et se laisser le monopole sur certaines applications. C'est gagnant pour eux et, il faut l'avouer, confortable pour l'utilisateur... tant qu'il n'essaie pas trop d'être indépendant, mais Apple sait se trouver un public de gens qui privilégient leur confort (comment ne pas écrire petit bourgeois en continuation naturelle de ma phrase ? ;)
Tant pis pour les développeurs qui veulent un peu de flexibilité, pour les applications qui veulent encourager le partage de code entre utilisateurs, les gens qui veulent apprendre à programmer et montrer leur appli à leurs amis sans payer la taxe... C'est un autre compromis, mais à choisir Java et un mobile qui chauffe ça ne me semble pas si mal.
On pourrait avoir le beurre et l'argent du beurre avec des outils de vérification statique sur des langages plus bas-niveau que Java, un bon sandboxing par défaut (par exemple NaCL et autres projets de LLVM aseptisé), et des modèles de sécurité robustes mais flexibles pour gérer le transfert de droit dans un contexte moins restrictif -- ce que permettraient la sécurité par capabilities par exemple, comme poussé par certains au sein de Googlet et ailleurs.
[^] # Re: Mes idéaux
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 1.
Personne ne t'oblige à le modifier. Tu gardes la même version du compilateur et voilà. Tu dois le modifier si tu comptes continuer à le compiler avec des versions suivantes du langages, ce que tu fais si tu veux profiter des nouvelles fonctionnalités ajoutées depuis son écriture (ou le combiner avec des projets qui le font).
C'est donnant-donnant, si tu veux profiter des nouvelles fonctionnalités ajoutées par les développeurs, tu acceptes aussi de prendre en compte les modifications qu'ils ont effectuées pour améliorer la qualité du langage ou se simplifier la vie.
Si tu refuses ce choix, pas de problème, (si l'implémentation est libre) le compilateur avec la version qui t'intéresse tu l'as, tu le gardes en prod depuis des années, et tu pourras continuer à l'utiliser tant que tu veux... et que tu ne changes pas ton environnement, et ouais, mais pourquoi changer un environnement qui marche en prod depuis des années de toute façon ?
[^] # Re: L'idéal
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 6.
Quoi, c'est une fonctionnalité ça ? Je ne dois pas comprendre parce qu'il me semble que c'est la base de la base. Je dirais plutôt que les langages qui ne l'ont pas (ou qui l'amputent d'une façon ou d'une autre) craignent un max (oui, dommage que ça incluse des langages aussi populaires que Coffeescript, ou les premières versions de Ruby).
[^] # Re: Hum ...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3.
Je n'ai pas besoin de ce "concept" pour me convaincre que ces fonctions font ce que je veux. Par ailleurs le fait qu'un appel est terminal ou non peut être vérifié par un critère syntaxique simple. Dans tous les cas son usage ne rend certainement pas le code "moins lisible".
Ta focalisation sur la récursion terminale est à mon avis signe du fait que tu te compliques la vie inutilement. Si tu réfléchis à ce que fais un programme fonctionnel en ayant en tête la façon dont c'est compilé vers du langage machine, l'explication de ce qu'est la récursion terminale est un peu technique et cela paraît être un cas particulier spécifique. Par contre si tu raisonnes simplement en terme de "évaluer c'est réduire le programme étapes par étapes vers le résultat", les bonnes propriétés de la récursion terminale viennent tout naturellement (au lieu de raisonner sur la taille de la pile, on raisonne sur la taille du programme réduit), et on n'a pas besoin d'une explication spécifique pour cela. C'est en fait une façon tout à fait valide et simple de penser aux programmes, indépendamment des aspects bas-niveau de la compilation.
Bien sûr, c'est normal, il n'y a rien de magique à écrire du code fonctionnel. Je n'ai jamais dit, moi, qu'une version serait "beaucoup plus lisible" qu'une autre. Mais c'est vrai que les miennes sont beaucoup plus courtes (grâce au choix opportuniste d'un langage à la syntaxe dépouillée), et aussi plus simples grâce à l'abandon du concept de variable modifiable; je n'ai pas besoin de différencier le calcul de la valeur et son retour.
Je suis d'accord et je ne suis pas pour utiliser la paresse par défaut à outrance. J'ai écrit mes exemples en Haskell parce que la légèreté syntaxique permet d'illustrer mon point très efficacement, mais dans la vraie vie j'utilise plutôt OCaml, qui est un langage strict par défaut.
Par ailleurs il y a des interactions intéressantes entre la paresse et la récursivité : en Haskell on ne recherche pas la récursivité terminale, et elle est même néfaste aux performances en général. Je te laisse creuser le sujet si ça t'intéresse, mais dans tous les cas les fonctions écrites ci-dessus sont strictes de toute façon, donc elles ne seront pas évaluées de façon paresseuse (le compilateur s'en rend bien compte).
Pour le fun, une version de fibonacci qui a la bonne complexité et utilise à fond l'évaluation paresseuse (c'est fun et ça fait un bon exercice de gymnastique intellectuelle, mais je ne recommande pas d'écrire comme ça):
(
zipWith (+)
envoie la liste des sommes terme-à-terme de deux listes de même taille, ettail
renvoie la liste privée de son premier élément.(!!)
est l'accès au n-ième élément d'une liste).