L’écosystème Python est en ébullition depuis la 3.4 et asyncio, tout le monde se sent pousser des ailes de faire évoluer la stack. La 3.5 vient réintroduire le fameux opérateur %
pour les bytes, sa disparition ayant fait bien chier tous les développeurs réseaux, on rajoute l’opérateur @
(qui ne fait rien, mais qui servira à numpy pour les opérations sur les matrices), il y a bien entendu le débat sur async
/await
et enfin le gros morceau : le type hinting.
C’est une période formidable pour la communauté car il rappelle que Python, un langage plus vieux que Java, un langage qui est maintenant considéré comme main stream – utilisé par des sociétés corporate et l’éducation nationale – ne restera pas sur ses acquis et va continuer à se réinventer.
Pour cette même raison, il existe un débat parallèle qui a lieu plutôt sur reddit, hackernews, la mailling list de Python-dev et Python-idea ou par PR github interposées sur des projets type pyrsistent. Il s’agit de faire de Python un langage fonctionnel.
La programmation fonctionnelle, c’est quoi ?
Ce n’est pas juste utiliser des fonctions.
Il y a plusieurs caractéristiques à la prog fonctionnelle, mais la plus importante est que tout est immutable (les données ne peuvent être modifiées), et par corollaire, que rien n’a d’effet de bord.
Ce qui signifie que dans un langage purement fonctionnel, une structure comme la liste n’aurait pas de méthode qui ne retourne rien comme append()
ou pop()
.
liste.append(element)
serait remplacé par liste + [element]
.
element = liste.pop()
serait remplacé par liste, element = liste[:-1], liste[-1]
Vous notez qu’on ne modifie pas la liste initiale, on recrée de nouvelles structures de données à chaque fois.
Une alternative serait que ces méthodes retournent une nouvelle liste plutôt que None
.
Bref, c’est comme si on avait que des tuples :)
Bien qu’on puisse créer des formes de programmations orientées objet qui n’impliquent pas de mutabilité, dans les faits les implémentations sont pour la plupart basées justement sur le fait qu’on puisse modifier les objets, aussi on oppose souvent FP et OOP.
Une autre caractéristique est qu’on doit pouvoir remplacer toute expression par son résultat suivant sans changer le sens du programme. C’est très élégant :
[1, 2, 3] + [4]
peut être réécrit [1, 2, 3, 4]
, et le programme aura toujours le même sens. Il n’y a pas de notion de référence dont il faut se soucier, seulement les valeurs. Cela élimine naturellement un paquet de bugs et il n’y a pas de bonne pratique à appliquer ou apprendre sur la modification des données puisque le langage vous force à coder proprement.
En programmation fonctionnelle, la récursion est donc souvent mise en avant. Par exemple, supposons qu’on calcule la taille d’une liste ainsi en programmation traditionnelle :
def size(l): s = 0 for x in l: s += 1 return s |
Voici une forme possible en programmation fonctionnelle :
def size(l): if not l: return 0 return 1 + size(l[1:]) |
La raison pour cela est qu’on peut substituer chaque expression à son résultat. Avec l = [1, 2, 3]
:
size(l)
devient size([1, 2, 3])
, qui devient 1 + size([2, 3])
qui devient 1 + 1 + size([3])
qui devient 1 + 1 + 1 + size([])
qui devient 1 + 1 + 1 + 0
qui devient 4
.
Quelle que soit la forme remplacée, le fonctionnement du programme est rigoureusement identique.
Il devient alors plus facile de prouver qu’un programme marche, de le tester, et surtout, de travailler de manière concurrente : si rien ne se modifie, nul besoin de lock et de synchronisation car rien n’est partagé et toute instruction a toujours le même comportement quel que soit le contexte.
Python possède déjà des features fonctionnelles
Comme en Python on peut programmer en utilisant plusieurs paradigmes, il n’y a rien d’étonnant à ce qu’on retrouve des amateurs de plusieurs écoles dans les core devs et les outils qui vont avec.
Bien que les listes, les sets, les objets et les dictionnaires soient mutables, les tuples, les chaînes et les entiers ne le sont pas. Par ailleurs, les fonctions sont “des citoyens de première classe”, ce qui est considéré comme un pré-requis pour la programmation fonctionnelle. Cette expression signifie qu’on peut manipuler les fonctions elles-mêmes, les créer dynamiquement, les retourner et les passer en paramètre (c.f: les décorateurs).
Même si ce n’est pas explicitement nécessaire pour faire de la programmation fonctionnelle, tout langage fonctionnel sérieux vient avec 5 outils de base :
map()
: appliquer une fonction qui transforme chaque élément d’un iterable pour en obtenir un nouveau.filter()
: appliquer une fonction pour filtrer chaque élément d’un iterable et en obtenir un nouveau.reduce()
: appliquer une fonction aux deux premiers élements d’un iterable, puis au résultat de cette fonction et à l’élément suivant, et ainsi de suite, jusqu’à obtenir un résultat.- Les fonctions anonymes: des fonctions qui peuvent se définir sans bloc ni nom.
- La récursion: une fonction peut s’appeler elle-même.
Python possède ces outils : map()
et filter()
sont des builtins, reduce()
peut être trouvé dans le module functools
et les fonctions anonymes peuvent être créés avec le mot clés lambda
. Et bien sûr, une fonction peut s’appeler elle-même.
Voyons comment créer une liste de carrés de tous les nombres impairs de 0 à 9 :
carres = [] for x in range(10): if x % 2: carres.append(x * x) |
La version fonctionnelle avec map()
/filter()
et des fonctions anonymes :
list(map(lambda x: x * x, filter(lambda x: x % 2, range(10)))) |
Néanmoins Python va plus loin, puisqu’il propose les listes/sets/dictionnaires en intension, qui ne sont ni plus, ni moins que que map()
/filter()
intégrés sous forme de syntaxe :
[x * x for x in range(10) if x % 2] |
Ce qui est non seulement plus concis mais plus clair et plus performant que les deux versions précédentes.
Donc, malgré des limites que nous verrons ensuite, Python est plutôt bien armé pour la programmation fonctionnelle. La présence des générateurs et tous les outils associés (itertools
, functools
, yield
, yield from
) viennent renforcer cet aspect orienté transformation plutôt que modification.
Il est par ailleurs déjà considéré comme une bonne pratique d’éviter les effets de bord le plus possible, d’utiliser des générateurs et des intensions. Bref, Python n’est pas le benêt de la programmation fonctionnelle.
Les limites de Python et de la programmation fonctionnelle
Malgré cela, il existe de sérieuses limites pour faire de Python un langage purement fonctionnel.
D’abord, les listes, les dicos et les sets sont mutables. Ensuite, la récursion est limitée par la taille de la stack (par défaut 1000 récursions), il n’y a aucune tail call optimisation. Enfin, les lambdas sont bridées à une expression.
Ceci n’est pas arrivé par accident, mais est le résultat d’un design du langage imposé par Guido Van Rossum, son créateur et BDFL.
D’abord, il faut bien se souvenir que raisonner pour modifier une structure de données, c’est beaucoup plus facile pour la machine ET l’humain que d’en recréer une.
Les algos de tris in place sont toujours plus rapides, les transformations in place prennent moins de mémoire, et l’accumulation est bien plus facile à concevoir, relire et debugger que la récursion.
J’ai déjà entendu des gens me soutenir que la FP était simple. Ces gens n’ont soit jamais essayé de former d’autres personnes à la programmation, soit sont des formateurs de merde. Je pense qu’au moins ce blog me donne un minimum de crédibilité sur mes qualités de pédagogue et je m’érige donc en tant que prophète de l’intelligibilité :
En vérité, je vous le dis, la programmation fonctionnelle, c’est dur à faire comprendre.
Erlang, Lisp et Haskel ne sont pas user friendly.
Ne me faites pas dire ce que je n’ai pas dis. Ce sont de beaux langages, des outils performants et utiles, mais Python est 100 fois plus facile à utiliser. Il n’y a aucune comparaison possible, ce n’est même pas sur la même échelle sur le graphe.
Ensuite, le monde n’est pas immutable : les choses ont des états à un instant t. En informatique, une connexion réseaux a un état, un système de fichier a un état, une session a un état, etc. Les langages fonctionnels ont donc recours à tout un tas d’astuces et d’enrobages pour les manipuler comme si ce n’était pas les cas. La fameuse “simplicité”, “élégance” et “pureté” qu’ils prônent en prend un coup.
Les meilleurs design patterns fonctionnels peuvent devenir un enfer pour travailler avec, les monades étant l’exemple le plus couramment cité. Déjà que c’est pas évident de faire passer la OOP, les générateurs, les classes et les décorateurs… Bonne chance pour les monades, et merci pour le poisson !
La limite de récursion et de la taille des lambdas est également parfaitement volontaire. Comprenez bien : tous les développeurs n’ont pas (et n’ont pas à avoir) le même niveau. Mettre un géographe devant une récursion avec injection de dépendance est aussi productif que de demander à un programmeur Web de calculer lui-même sa projection mercator.
Si demain la récursion devient plus facile, les dev chevronnés vont massivement l’utiliser même quand ce n’est pas indispensables : donnez un jouet à un geek, et il va s’en servir. Mais la lisibilité est primordiale, c’est une feature de Python. Les codes deviendront alors soudainement significativement plus durs à lire et à débugger, pour un gain minime. En effet, la récursion ne m’a JAMAIS manqué en Python. Les développeurs ne sont pas raisonnables, ils ne vont jamais se dire, je vais m’assurer que ce code sera facile à lire pour un débutant ou moi sans café, ils vont utiliser tous les outils à leur disposition. Guido a donc volontairement limité les outils à ce qui garde un code facile à appréhender.
C’est ce qui a fait le succès de Python jusqu’à présent. C’est un langage facile à prendre en main, et productif. Lire le code d’un autre est simple.
Lire le code Lisp d’un autre n’est PAS simple.
Une stack trace d’une erreur en pleine récursion est pleinement inutile.
Le callback hell de Javascript plein de fonctions anonymes en cascade est dégueulasse.
Vous allez me répondre : “les gens n’ont qu’a programmer proprement” où “il y a des outils pour ça”
Outre le fait que pouvoir coder en Python avec juste notepad et la console est une des ses meilleures features, c’est ignorer la nature humaine.
On pourrait penser que quelque chose d’aussi simple qu’une indentation propre et un espacement consistant est la base.
Mais non.
Jetez un œil à n’importe quel programme JS ou PHP de petite ou moyenne taille, et vous trouverez invariablement des blocs de traviole, des variables globales, des espaces qui dégueulent à droite et à gauche et des saloperies en tout genre.
Python force l’indentation. Python a un PEP8.
J’adore ça.
Ca amène les porcs à coder plus proprement.
C’est pareil pour les lambdas et la récursion. On force la main aux gens déraisonnables pour qu’ils ne fassent pas de connerie. Désolé, je n’ai pas confiance dans les barbus pour créer des choses user friendly. Et moi, j’aime ça, le code accessible. Fuck la pureté.
Si vous voulez des trucs tarabiscotés, Lisp le fera très bien.
Mais ce n’est pas Lisp qui a autant de succès, c’est Python. Et vous savez pourquoi ?
Parce qu’il est productif, facile, lisible, et avec une énorme communauté pleine de diversité car il est accessible à tout ce monde.
Bref, vous l’avez compris, je suis contre faire de Python un langage purement fonctionnel si c’est au prix de tout cela.
Maintenant, il existera peut être des moyens techniques pour rendre des implémentations d’outils fonctionnels aussi performants et lisibles que leurs versions non fonctionnelles.
Ca a été le cas avec les intensions : de longues chaines de map()
et filter()
illisibles deviennent claires avec une belle intension. Et c’est clairement un gain par rapport au for
+ append()
traditionnel. Mais pour les cas moins simple, on a toujours la possibilité d’utiliser cette méthode, qui est facile à lire, à comprendre et à débugger.
Je gage qu’un compromis intelligent, voire carrément malin, puisse être trouvé en la matière.
Mais s’il vous plait, ne cassez pas Python juste pour avoir un nouveau jouet, vous scierez la branche sur laquelle nous sommes tous assis en train de boire l’apéro en rigolant.
C’est un reproche qui a été fait au type hinting : si on offre cette possibilité, elle sera utilisée, et tout code avec ces annotations est significativement plus moche. Si demain la majorité des projets en font un pré-requis, beurk.
Je ne pense pas que ça arrivera car c’est un sacré effort supplémentaire de les écrire. Donc on le fera uniquement si ça vaut le coup : par exemple un gros projet ou une lib avec une bonne base d’utilisateur.
Ce n’est pas le cas pour la programmation fonctionnelle : son coût d’écriture est beaucoup plus bas que celui de la relecture. On paie plus tard. Et ça les gens ne savent pas gérer. L’humain est naze pour investir dans le futur, c’est pour ça que nous consommons les ressources de notre planète comme des goinfres, qu’on pollue à foison et qu’on laisse des dirigeants voter des lois liberticides.
Bonnes pratiques
Bien que je suis clairement en faveur de mettre un frein à l’enthousiasme des programmeurs FP tant qu’un terrain d’entente intelligent ne sera pas trouvé, ça ne veut pas dire qu’on ne peut pas bénéficier de leurs propositions.
En effet, sachez que vous serez un meilleur programmeur, et que votre programme gagnera en qualité, si vous appliquez quelques principes de programmation fonctionnelle et utilisez les outils déjà existant en Python pour en tirer parti.
Le tout est de ne pas en abuser, et surtout, de ne pas s’interdire de pondre un code bête et simple quand c’est plus rapide ou plus clair.
D’abord, limiter les effets de bord est toujours une bonne chose.
Si vous vous trouvez à modifier une structure de données dans une fonction alors qu’elle l’a reçue en paramètre, réfléchissez à deux fois. Est-ce vraiment nécessaire ? Ne pouvez-vous pas faire autrement. A part pour quelques cas très précis (tri sur place, passes multiples, cache, etc), il rare d’avoir à le faire.
Utilisez les intensions et les générateurs. Utilisez itertools
. Utilisez functools
. Ces outils demandent un peu d’apprentissage, mais ils sont formidables : plus performants, plus expressifs, plus lisibles.
Il vous faudra un peu de pratique pour trouver un bon équilibre. Arrive un point où on les utilise partout, ce qui rend le debuggage plus difficile. Passé ce stade, revenez en arrière, calmez-vous, respirez, retourner à l’impératif, ce n’est pas non plus retourner au visual basic.
Quand vous faites de la programmation concurrente, voyez si vous ne pouvez pas isoler vos états derrière des services, et faites communiquer les services entre eux via des messages immutables. Votre application sera plus claire, plus stable.
D’une manière générale, il est bon de diviser son code en petites unités, fonctionnelles justement, qui se passent des valeurs les unes aux autres.
Mais au final, n’oubliez pas la définition d’un bon code :
- Ca marche.
- Ca continue de marcher.
- On comprend pourquoi ça marche.
Le reste n’est que drosophilie sodomite.
P*tain c’est beau ce que tu écris !
C’est pour ça que je suis ton blog alors que j’ai jamais touché à Python. Tu as l’expérience d’un Architecte logiciel, la clarté de l’eau de Volvic et une catégorie Cul.
Bref, désolé pour ma non-contribution au débat mais je voulais quand même dire merci pour cet article que je peux transposer très simplement dans mon domaine de compétences (.NET) et ton éclairage sur la programmation Fonctionnelle :)
Bonne journée !
Coin \_o< !
Article bien sympathique, comme d’hab :)
J’ai relevé une coquille dans le passage suivant :
Cela ne devrait-il pas être plutôt :
My two cents !
Haha, je dois avouer que ce post est vraiment top :)
J’ai juste une question: les gens ont du mal a saisir la prog avec lisp, haskell, erlang, n’est ce pas un grand peu parce que bien souvent, ils ont d’abord formatté leur maniere de penser avec c, java, php, etc, …?
Enfin bref, merci pr l’article.
Also l’avant dernier point de la conclusion de l’article. ;-)
Sinon, je suis globalement d’accord avec cet article. Car en effet, parfois se passer de la FP et opter pour un simple “for + append()” peut être bien si l’on cherche a produire du code Python qui peut être lu et compris facilement par les devs de d’autres languages comme le C++, mais pure un code purement pythonesque avec un bon mélange des deux methods peut produire du code beau et performent. Et c’est toujours plus drôle à coder. :-)
Tu parles d’un débat “sur la mailling list de Python-dev et Python-idea, sur reddit et hackernews et par PR github interposées,” J’ai à peu de chose près rien vu passer. Tu as des liens ?
J’ai joué un peu sur le sujet dernièrement et je me suis fait mordre par la limite d’appels récursifs. C’est dommage de ne pas avoir de TCO mais comme tu l’écris c’est en accord avec les principes derrière python donc c’est OK.
Haskell est un langage élégant avant tout parce que son compilateur fait des trucs très sophistiqués, je pense qu’il est difficile de se rendre compte de la façon dont le code est effectivement exécuté par la machine. Des éléments comme la TCO, le pattern matching ou la lazy evaluation ne sont pas des fonctionnalités que j’imagine simple à mettre en oeuvre.
Si je veux jouer avec, je partirais sur du Haskell ou autre et je laisserais python tranquille.
@LeMeteore: oui et non. C’est vrai que ça n’aide pas. Mais il y a aussi une autre raison : l’impératif est facile à expliquer en terme d’activité de la vie tous les jours : tu fais ça, puis tu fais ça, puis tu fais ça. C’est du pas à pas, ça map avec notre manière de penser une tache. Le fonctionnel c’est une construction plus proche des maths, et beaucoup plus abstrait. Une chose qui n’arrange rien : les programmeurs fonctionnels sont généralement des gens qui ont un bon niveau technique, mais pas un bon niveau pédagogique.
@Xavier Combelle : sur reddit/r/python, on voit régulièrement des topics passer. Sur hacker news, c’est dans les comments. Sur python idea et dev c’est vrai que ça s’est calmé car les gars se font remballer rapidement. Si je tombe sur un lien dans le future, je le posterai ici si j’y pense car c’est vrai que question citation je pêche un peu :) . Là j’ai rien sous la main, j’ai pas écris l’article ne réaction à un truc, mais plus parce que c’était dans la lignée des changements à Python.
@Galea et @TitusCrow : en effet j’ai merdé.
@xavier combelle : lol, ben justement http://www.reddit.com/r/Python/comments/33qzzf/what_features_should_python_steal_from_other/
@Sam Merci de ces précision (le post que tu as cité de reddit était l’ensemble de mon “peu de chose près”)
Merci pour l’article, c’était très intéressant.
La dernière section me rappelle une bonne question sur SO, sur Comment améliorer son python. Apprendre la programmation fonctionnelle avec Haskell était un gros point de la réponse la plus votée.
Dans la même lignée, un projet dont on a un peu parlé dernièrement : https://github.com/tobgu/pyrsistent
Article interessant.
Je saute sur le troll :)
J’ai des enfants en primaire qui voulaient apprendre “a programmer”, j’ai reflechi sur quel language choisir et j’ai decide de prendre Haskell car c’etait le plus proche de la logique mathematique qu’ils connaissent avec une syntaxe simple (ex: addition a b = a + b) et pour l’instant ils s’en sortent plutot pas mal en jouant dans ghci.
On est d’accord que les monades c’est pas pour tout de suite, mais ils ont deja plus ou moins compris l’intuition derriere les functors.
Il y a des codebases en Haskell qui depassent 2 million de lignes. Le typage fort et statique (pas forcement fonctionnel), le property testing (quickcheck) et l’isolation des effets de bords facilitent fortement la maintenance.
Et l’intransigeance du compilateur sur les types permet de deceler beaucoup d’erreurs a la compilation.
Mouais, entre
et
Je trouve la version fonctionelle legerement plus lisible ;)
Apres je suis totallement d’accord que d’essayer de marrier un language objet/imperatif et un language fonctionnel est une idee a la con, et un coup a se retrouver avec un gloubi boulga inutilisable qui rassemble le pire des deux mondes.
Ça c’est un chouette article !
Quelques fautes.
range( 10 )
et vous trouverez invariables des blocks de traviole, des variables globales, …
Syntax error from parser : “langue française”.
L’humain est naze pour investir dans le future,
from orthographe import futur_sans_E_a_la_fin
@Eric. On doit pas tout à fait avoir la même notion de lisible. Pour moi avant d’être lisible un code doit d’abord être compréhensible et j’avoue me demander encore que fait le code que tu as donné qu’il soit en python ou en haskell. Selon ce critère ni l’un ni l’autre n’est lisible. (bon j’avoue je n’ai quelques bribes de connaissances en haskell)
Depuis deux/trois ans j’ai malheureusement le sentiment que les bonnes
âmes vont casser python.
C’est toujours le même piège; le langage devrait devenir idéal pour tout.
C’est débile et pourtant c’est ce qui arrive à tous les langages au fur et
à mesure qu’ils deviennent mainstream.
Encore deux/trois ans comme ça et python sera une grosse bouse comme
java ou c++ ou d’autres.
Dommage.
@Xavier Combelle et Eric : outre que le fait que ces deux codes ne font pas la même chose et donc ne peuvent pas être comparés, c’est typiquement le cas d’un code qui mérite des commentaires (fournis d’ailleurs dans l’article dont il est extrait).
La 3.5 vient réintroduire le fameux opérateur % pour les bytes, sa disparition ayant fait bien chier tous les développeurs réseaux
Pourquoi spécifiquement les développeurs réseau ?
Parce que les protocoles réseaux ont souvent besoin de manipuler des bytes selon des templates. Perso en tant que dev web, j’en ai rien à branler que “%” marche pas pour les bytes puisque j’utilise que des strings.
Article très intéressant, merci beaucoup.
Apparemment Java suit la même évolution (plus lentement et à doses plus homéopathiques, forcément) avec Java 8 et l’introduction des lambdas et de la Steam API, qui font de java un langage OO avec des éléments fonctionnels.
Quelques typos, désolé si je fais le grammar nazi, mais tant qu’à faire, si déjà je les relève toutes, je me dis autant les signaler toutes.
corolaire –> corollaire
on recréé de nouvelles structures –> on recrée de nouvelles structures
dans les fais –> dans les faits
le langage vous forcer à coder –> le langage vous force à coder
[x * x for x in range(0) if x % 2] –> [x * x for x in range(10) if x % 2]
l’accumulation et bien plus facile –> l’accumulation est bien plus facile
ce que je n’ai pas dis –> ce que je n’ai pas dit
Les langages fonctionnelles –> Les langages fonctionnels
Les codes deviendrons alors soudainement significativement plus dur –> Les codes deviendront alors soudainement significativement plus durs
des espaces qui dégueules –> des espaces qui dégueulent
dans le future –> dans le futur
comme des goinfre –> comme des goinfres
Si vous vous trouvé –> Si vous vous trouvez
Utilisez fonctools –> Utilisez functools
Ces outils demandent un peut –> Ces outils demandent un peu
Passer ce stade –> Passé ce stade
et faites communiquer es services –> et faites communiquer ces services
leurs versions non fonctionnels
dans le future
comme des goinfre
Si vous vous trouvé
Utilisez fonctools
Ces outils demandent un peut
Passer ce stade
et faites communiquer es services
@Sam, vu qu’ils reviennent sur des changements, ils voudraient pas remettre le cmp ? J’en suis fan perso.
@Eric, plus lisible ? plus courte à la limite mais j’y bite rien perso, a part que tu geres des tableaux d’int et que tu as l’air de les ordonner.
Au moins la version python et ses noms à rallonge te fait comprendre que tu as une liste d’éléments (non null), que tu les range et que tu retourne leur valeur et leur position (dans cet ordre).
@Eric :
Bravo, en un exemple tu viens de me faire passer l’envie de toucher à ce langage, c’est complètement illisible le deuxième exemple ! Il faut avoir une formation spécifique pour comprendre ce machin.
L’exemple au dessus est déjà pas lisible mais au moins les noms sont explicites et les fonctions pas dures à comprendre.
Très bon article, à l’exception d’une faute d’orthographe :
s/intension/intention/g
On m’a conseillé ce bouquin pour apprendre la prog fonctionnelle :
http://book.realworldhaskell.org/read/
Merci !
Avant de commenter, et puisque toute opinion est subjective, je tiens à
préciser que python est mon language de prédilection pour tout depuis
plusieurs années, mais que j’aime énormément la programmation fonctionnelle,
lisp etc…
Déjà, merci d’avoir commencé par l’immutabilité. Ça a l’air de rien mais
c’est difficile de prendre au sérieux les articles qui présentent la
programmation fonctionnelle avec une fonction de fibonacci réccursive
(généralement pourrie d’ailleurs).
Tout les bons points de la programmation fonctionnelle sont mis en avant, à
par la lisibilité avec laquelle je suis mitigé.
D’un côté, je suis d’accord que comme tout le monde enseigne la programmation
impérative, ça parait plus simple que de penser en fonctionnel. Je ne pense
pas que ça soit vrai pour tout les domaines cependant. Quelqu’un qui vient du
bash par exemple et n’a jamais vu une ligne de C sera très à l’aise avec
l’idée de flux et de transformation. Sans parler des matheux.
Mais pour moi le point est autre… J’aime l’idée de décrire ce que sont les
choses plutôt que comment elles sont faites.
Une meilleur syntaxe de composition de fonctions aurait des avantages. Je
comprend néansmoins que l’on ai pas envie de diviser le langage, après tout
il ne devrait y avoir qu’une bonne façon de faire les choses en python…
mais dans la vrai vie c’est pas si simple.
Voici deux articles qui montrent assez bien ce que je veux montrer (même auteur):
http://hackflow.com/blog/2014/06/22/why-every-language-needs-its-underscore/
http://hackflow.com/blog/2013/10/08/abstracting-control-flow/
Depuis que je me suis vraiment mis à la programmation fonctionnelle, je me
retrouve souvent en python à définir de petites fonctions d’une ligne ou deux
qui ne changent quasiment rien mais qui me permettent de nommer plus finement
ce que je suis en train de faire, mon code est beaucoup plus lisible comme
cela.
Et puis il y a lisp…
Autant je suis assez d’accord avec toi sur le code lisp de quelqu’un d’autre
étant difficile à aborder, ça n’a pas grand chose avec son côté fonctionnel
(d’ailleurs Common Lisp est souvent plus codé en style impératif ou OO). Le
soucis de lisp est autre: il est trop puissant. Son concept d’écrire un
langage de programmation adapté à chaque problème serait génial si les
programmeurs étaient linguistent… ce que peu sont. Du coup chacun exprime
très précisément et élégament sa pensée, mais nous pensons tous différemment.
Cela couplé aux macros (toujours plus de puissance!) et au manque d’espaces
de noms en lisp donne un code très difficilement abordable pour un
non-initié.
Du coup, que faire ?
Je pense que dans l’ensemble python est bien comme ça. Oui, j’aimerais qu’il
y ai plus de possibilités fonctionnelles, mais si je veux un langage
fonctionnel je peux en prendre un autre. Les librairies permettent de toute
manière d’apporter l’essentiel de ce qui est nécessaire lorsque le besoin
s’en fait sentir.
Je pense que de petites modifications seraient intéressantes cependant. J’ai
en tête notamment la TCO. Sans partir dans les optimisations compliquées au
niveau du compilateur (on est d’accord pour dire que python n’a pas besoin de
ça) il y a une solution qui est très simple à mettre en œuvre et améliorerait
significativement les performances: le système de trampoline.
Il s’agit simplement d’ajouter un accumulateur:
Sans trampoline
def factorielle(n):
if n == 0:
return 1
return n*factorielle(n-1)
Avec trampoline
def factorielle(n, acc=1):
if n == 0:
return acc
return factorielle(n-1, acc*n)
Sans rentrer plus rentrer dans les détails, c’est quelque chose qui est assez
simple à mettre en place et certaines librairies le proposent comme
décorateur. Si c’était fait par défaut, on aurait juste des fonctions
réccursives qui prennent moins de mémoire, et possiblement moins de soucis de
limite de récursion. Je ne pense pas que tout le monde se jetterai sur la
reccursion directement, mais le fait est que dans certains cas c’est juste
incroyablement plus lisible qu’une boucle for parce que l’on décrit ce que
l’on fait et pas comment on le fait et que contribuer à l’idée que la
reccursion est forcément inefficace est dommage et dangereux.
Bon, tout ça pour dire que même si je ne suis pas toujours d’accord, c’est
quand même un putain de bon article :)
Y’a eu des débats sur python-dev lors de la mise n place du PEP sur l’introduction de l’opérateur @, certains voulaient qu’il serve aussi pour les compositions de fonction: f(g(x)) deviendrait f@g(g), et surtout on peut poser h = f@h au lieu d’utiliser un lambda. Ça a été rejeté, mais je ne me souviens plus des raisons. :(
Par rapport à la programmation fonctionnelle, c’est peut être adapté à certains domaines mais ça doit très mal marcher dans le calcul numérique où on manipule de grosses données qu’on ne peut pas dupliquer à tord et à travers à chaque fois qu’on doit le modifier.
Salut !
Merci pour cet article, ainsi que pour tous les autres d’ailleurs, intéressant et assez complet.
Pourtant il y a quelques points sur lesquels je ne suis pas d’accord, et en particulier : L’impératif c’est plus intuitif et Le fonctionnel c’est moins lisible.
Par ailleurs je trouve la remarque sur la difficulté du debugging plutôt maladroite, en effet en l’absence d’effet de bords on évite quand même une part énorme de debug justement parce que les objets ne sont pas modifiés par des bouts de notre code à droite à gauche… Pour avoir fait pas mal d’OCaml et pas mal de python, je peux affirmer que mon temps passé à debugger en python (que je maîtrise moins, c’est vrai), est sensiblement plus long, quoique ça ait plus à voir avec le typage statique qu’avec le côté fonctionnel.
Mais prenons un petit exemple de code facile à lire, qui utilise des fonctions d’ordre supérieur simplement (je suis plus à l’aise en OCaml qu’en Haskel ou en Lisp, mais je ne doute pas que ce soit pareil dans la plupart des langages fonctionnels !) :
let twice f x = f (f x);;
let fourtime = twice twice;; (* Difficile de faire plus clair quand même ;p *)
let succ x = x+1;;
let add_exclamation_mark s = s ^ "!";; (* (^) : string -> string -> string est la concaténation *)
assert (fourtime succ 0 = 4);; (* Pas de surprise ici, s(s(s(s(0)))) = 4 *)
Printf.printf (fourtime add_exclamation_mark "FP wins");; (* Prints "FP wins!!!!" =D *)
Ce genre de choses est facile à lire et vraiment intuitif d’un bout à l’autre, parce que “fourtime” c’est “twice twice” tout simplement… Et même avec un typage statique fort, on peut utiliser ça sur les fonctions du type qu’on veut, le polymorphisme nous assure que ça passe (rien avoir avec le duck typing de python dont on ne sait jamais s’il ne va pas renvoyer un None qui va ensuite se propager et causer un bug à un autre endroit inattendu qui force à passer en mode debug).
Par ailleurs, pour des gens qui ont commencé en impératif c’est sans doute plus intuitif comme ça, mais j’ai vu une bande de matheux programmer pour la première fois, et je vous assure que la version récursive de la fonction puissance est beaucoup plus intuitive pour eux, parce qu’elle colle à la définition en math : on écrit la définition, et ça donne un programme qui la calcule, formidable non ?
let rec power x n = match n with
| 0 -> 1
| n -> x * (power x (n-1))
Certe ça utilise tu pattern matching, c’est encore autre chose, mais si on veut l’écrire if n = 0 then () else () ça marche aussi, c’est juste que cette syntaxe est tellement proche de la façon dont on l’écrirait en math que c’est est presque magique !
D’ailleurs l’autre exemple qui m’est venu en tête, c’est le quicksort, je vous laisse apprécier cette page wikibooks (difficile de dire que la version impérative, quoique dans ce cas là plus efficace à cause de l’absence de récursivité terminale (qui n’existe de toute façon pas en python si j’ai bien compris) est plus lisible et/ou plus intuitive quand même !)
Bref, le récursif n’est pas intrinsèquement moins lisible (le Lisp par contre ?) et pour de nombreux problème il est plus intuitif je trouve (essayez de coder un quicksort purement impératif sans vous planter et sans avoir à debugger, je pense que pour beaucoup ça va demander plusieurs essais… Mais je suis près à relever le défi en OCaml quand vous voulez !)
Deux (grosses) remarques supplémentaire:
(i) il faut voir à ne pas opposer orienté objet et fonctionnel, le O de OCaml est là pour le rappeler, on peut mixer les deux et ça n’est hérésie de le faire, ça permet juste d’associer les avantages des deux côtés, et il n’y a pas vraiment d’inconvénient.
(ii) il ne faut pas confondre pureté et fonctionnel : Haskel est pur (d’où le problème des monades pour encapsuler les effets de bords par exemple), mais on peut utiliser la pleine puissance du fonctionnel dans un langage qui a des effets de bords, c’est le cas d’OCaml (on va croire que je suis là pour faire de la pub, mais non !), et une fois encore on a pas d’inconvénient à rajouter le fonctionnel, juste des avantages supplémentaires en terme de lisibilité, de modularisation du code et de debugging !
Par ailleurs je ne comprends pas pourquoi des gens qui apprécient le fonctionnel voudraient se battre pour l’intégrer dans python, ils feraient mieux de le fuir plus sérieusement, je ne me bats certainement pas pour que ce soit intégré à Python, je réagis juste à la façon pernicieuse que tu as de décrire le fonctionnel comme une feature qui complique les choses et donne plus de boulot, je vois vraiment ça dans l’autre sens, ça évite beaucoup de débug, segmente le code et le rend plus facile à relire (relire un bout plutôt que de devoir suivre le flux de contrôle), permets d’écrire des choses plus proches des concepts qui sont derrière, etc. !
Bref, je ne suis pas tout à fait d’accord non plus. Mais ça n’en est pas moins un bon article, et dans le fond je suis d’accord que ça n’a pas forcément sa place en python !
Pouet.
J’avais vaguement envie de me jeter dans l’eau du débat, mais y’a plein de gens intelligents et motivés qui ont écrit des gros pavés intéressants au-dessus de moi, alors je vais me contenter de communiquer mon amour pour ce blog. Je suis plutôt d’accord avec cet article, j’aurais beaucoup de choses à en dire et beaucoup de choses à répondre à la moitié des pavés sus-nommés, mais il est tard, j’ai la flemme. En tout cas, excellent papelard, plein d’amour, bonne nuit !
Super article.
Sans être utilisateur de python au quotidien, j’aime quand même bien suivre le site pour le style des articles, et la pédagogie (et la section cul).
Mais ce billet est tellement bien foutu qu’il m’a donné envie de dépasser la flemme pour poster ce commentaire inutile.
C’est intéressant dans le cadre de python ET de la programmation en général. Ça me donnera
Excellents commentaires.
Je vais devoir un peu étayer ce que je t’entends par “plus dure à lire” et “plus dure à débugger”.
1 – plus dur à lire
Les langages fonctionnels encourages un style où les instructions sont compressées et chaînées, où il y a moins de mot clés, et plus de symbole. Cela augmente ce qu’on appelle “la charge cognitive”, c’est à dire le nombre de cycle cpu que le cerveau doit consacrer, non pas à la tache de comprendre le sens du code, mais à décortiquer la structure.
Quand on écrit du Python, les mots clés et les symboles sont placés de tel sorte que le code se parse avec moins d’effort, à niveau d’expérience égale avec le langage. C’est la raison pour laquelle le “:” pour ouvrir les blocs, dont le parseur pourrait se passer, est si important : c’est un marqueur qui permet au cerveau de savoir, sans avoir à chercher plus loin, ici est el début d’un bloc.
Le fait que de nombreux langages fonctionnels se rapprochent de l’expressivité mathématique n’est pas, pour les non mathématiciens (qui sont la majorité des programmeurs), une qualité. Un dev Web, un sysadmin, un intégrateur, un dev ops ou testeur ou un créateur d’UI est plus à l’aise avec une notation proche d’un langage parlé. Structuré certes, mais plus naturel.
C’est la raison pour laquelle il y a plusieurs langages : tous les goûts et besoins sont dans la nature.
Mais cela à aussi une conséquence importante, c’est que Python est un bon langage partout. Pas juste pour de l’algèbre linéaire. Ou du web. Ou du sys admin. Il est productive, et s’adapte à toutes ces taches, parce que son style est plus passe partout. C’est pour cette raison que beaucoup de scientifique abandonne R pour Python : Python est moins bien que R pour exprimer les maths, mais pour faire d’autre chose, c’est mieux.
2 – Difficile à debugger
Debugger le code, c’est avant tout le lire. Et si lire du code est plus difficile, debugger est également plus dur.
Mais ce qui rend particulièrement difficile à debugger, c’est la recursion.
D’abord, la logique elle-même de la récursion peut amener à des bugs subtiles et difficile à trouver. Rien que rater sa condition de sortie est vachement plus facile que rater sa boucle.
Néanmoins la vrai problème vient dans la nature meme de la récursion : ça rend la stack trace inutile. Et lancer un break point dans une récursion est une horreur.
Enfin, la logique de flux, et le côté compact des expressions, rend tout debuggage plus compliqué. Je m’en suis rendu compte en abusant des générateurs en Python.
Quand on fait :
truc((chose(x) for x in machine(bar) if foo(x)).bidule()
C’est très expressif. Maintenant, si ça fait pas ce qu’on veut, qu’est-ce qui a foiré ? bar, truc, chose, machine, foo ou bidule ? On est obligé de décomposer son code en plusieurs ligne manuellement et lancer un debuggage pas à pas. Or les langages fonctionnels adorent ce genre de ligne.
Je concède par contre que le fait que tout soit immutable rendent naturellement le code plus fiable.
@Lucas: intension avec un “s” car on parle d’intension mathématique. mais il y a plein d’autres fautes, ne t’inquiète pas :)
Le débat d’expert : entracte
Super article.
Sans être utilisateur de python au quotidien, j’aime quand même bien suivre le site pour le style, et la pédagogie (et la section cul).
Mais ce billet est tellement bien foutu qu’il m’a donné envie de dépasser la flemme pour poster ce commentaire inutile.
C’est intéressant dans le cadre de python ET de la programmation en général. Ça me donnerait presque envie d’un article plus général sur les différents paradigmes. Enfin, y aurait de quoi écrire un bouquin.
Bref, vu que je le fais jamais et qu’il faut pas qu’il y ait que des commentaires négatifs sur internet (même si c’est pas trop le genre d’audience que vous avez) : GG pour l’article, et pour tous les autres. Merci pour ce que vous faites. :)
@Artymort :
Désolé mais pour moi ton premier exemple n’est pas DU TOUT intuitif ni facile à lire.
Que ce soit concis et exact je n’en doute pas mais encore une fois si on a pas une formation spécialisée au préalable on y comprend rien du tout.
Pour être clair, intuitif c’est : sans avoir de formation spéciale, je suis capable de comprendre globalement ce qu’il se passe dans le code. La je vois le code et la seule intuition que j’ai c’est “mais qu’est ce que c’est que ce bordel ?” :
– c’est quoi let ? Quel rapport avec le sens “normal” de let en anglais ?
– twice twice ? Sans opérateur ? ça se comprend mais il aurait quand même été mieux avec un o ou @ pour coller aux maths, et encore faut avoir fait des maths.
– Sans le commentaire, savoir que ^ fait de la concaténation … ben non c’est pas possible, pas intuitif du tout. Encore une fois c’est concis mais je m’en fous que ce soit concis, taper du code c’est pas si long, c’est le débugguer qui prend du temps !
– les doubles ;; à chaque ligne rajoutent de l’information inutile partout et rendent la lecture plus fastidieuse
J’ai vu cette coquilles :
tous les nombres impaires -> tous les nombres impairs
Il en reste encore ? Diantre !
@xavier combelle : http://www.reddit.com/r/Python/comments/34abto/is_there_an_implementation_of_python_without_the/ ^^
@dineptus
Je pense que par formation spécialisé ici tu parles juste de s’habituer à la syntaxe, assez différente de celle du python j’en conviens, mais qui a son charme.
Dans l’ordre :
C’est juste le sens usuel du let en anglais : “Let there be light”, “Let it be”, “Let n be an integer” ou “let n = 0″, ici on écrit juste “let double x = 2x”. (On peut aussi écrire “let double = fun x -> 2x” si on veut vraiment voir apparaître un lambda)
Il ne faut pas de o ou de @ ici, tu appelles twice avec comme premier argument twice, ce n’est pas de la composition de fonction ! Le but était justement d’illustrer l’utilisation de fonction d’ordre supérieur. (PS : un @ pour coller aux maths !?)
Les deux points suivants attaquent la syntaxe du langage et ne sont pas pertinent puisqu’on parle des fonctionnalités. Je pourrais rétorquer que donner une sémantique au retour à la ligne ou aux tabulations comme en python me hérisse le poil, mais ce ne serait pas pertinent non plus. Par ailleurs les doubles ;; n’apparaissent ici que parce que je l’ai écris comme pour une exécution au toplevel, en pratique il suffit d’un seul, et dans la plupart des cas d’aucun (cf le 2e exemple, plus proche de ce qu’on fait en pratique, il n’y en a pas du tout).
@Sam
Répondre à Sam demande plus de temps, je ne suis pas d’accord avec toutes les objections formulées. Mais je suis d’accord que tous les goûts sont dans la nature ! Et je suis plutôt d’accord avec ton article, intégrer tout ça à Python n’est pas forcément une bonne idée.
Mais je dois dire qu’en plusieurs années d’OCaml, je n’ai jamais eu à regarder une trace d’exécution, pas une seule fois. Alors je suis d’accord avec le fait que c’est en grande partie grâce à une certaine rigidité du langage (typage statique fort en particulier), les “None” qui viennent d’erreurs de typage idiotes qui se propagent en python sont ma hantise et c’est souvent à cause d’eux que je dois debugger.
Enfin, si je suis tout à fait d’accord pour dire que python est pratique parce qu’adaptable dans toutes les situations (et c’est pour ça que je m’en sers de temps en temps !), je ne dirais pas pour autant que c’est un “bon langage partout”, il y a plein de choses pour lesquelles le fonctionnel est un atout majeur (then again, le quicksort est parlant !) et pour lesquelles python n’est pas bon.
@Artymort Franchement je ne te suis pas du tout quand tu dis que le quicksort est parlant. En python j’ai l’impression de lire de l’anglais, en lisp ou ocaml (langages que le ne connais pas) je n’ai aucune idée de ce que signifient les différents symboles utilisés. On retrouve ce que disait Sam sur la charge cognitive.
Bien sûr avec de l’habitude on y arrive sûrement, mais c’est pas pour ça que c’est plus lisible. Mais bon, c’est difficile d’être objectif sur des choses auxquelles on est habitué (cette remarque fonctionne bien sûr dans les deux sens)
Quand on dit que python est “un bon langage partout”, on ne dit pas que python est “le meilleur langage partout”. Dans quasi tous les domaines il y aura un autre langage plus adapté, mais qui sera complètement inutile dans tous les autres domaines. Python permet de faire quelques chose de bien quel que soit le problème, sans forcément avoir d'”atout majeur”.
@kontre (après j’arrête)
Bon, une fois encore une critique sur la syntaxe (c’est à dire sur les 30 minutes que ça prend de s’y faire), on peut préférer la syntaxe de python, mais là on parle plutôt des fonctionnalité et de la programmation fonctionnelle en général.
Pour l’exemple du quicksort, je trouve que ça illustre bien la critique faite au debug, alors j’en ai écris une version ici (il semble que je ne sois pas bon pour insérer du code dans les commentaires et en plus on y gagne la coloration syntaxique \o/).
Il n’y a pas d’indice sur des tableaux, pas de boucle, la logique est très claire “on coupe en deux selon un pivot, puis on trie récursivement les deux extrémités et on met bout à bout”, pas besoin de debugger quelque chose comme ça parce qu’il n’y a pas vraiment de source d’erreur possible.
Alors que si j’écris la même chose en impératif, que ce soit en python ou en OCaml, je vais peiner, parce qu’il faut gérer de nombreux indices qui se baladent dans des boucles imbriquées, savoir quand un veut du +1/+0/-1 sur les indices, et que s’il y a une erreur elle ne sera signalée par personne, elle se propagera et le résultat n’aura aucun sens, au programmeur de comprendre pourquoi.
Outre le fait qu’encore une fois cet exemple est dur à lire, tu utilises :
Encore un fois, je ne dis pas que ce n’est pas élégant, je dis que la simplicité du résultat ne rend pas justice à la complexité du contexte que ça génère.
@Artymort :
S’habituer à la syntaxe est beaucoup plus long que tu ne le penses surtout quand on est pas amené à programmer dans ce langage tous les jours.
Je dois encore réfléchir à la syntaxe JavaScript par moment et pourtant j’en code très souvent.
Python c’est juste de l’anglais on comprend globalement ce qu’il se passe sans effort (après, pour se rappeler de tout c’est pas le cas mais au moins on peut lire du code sans avoir besoin d’y penser pendant des heures).
Après tu explique la syntaxe et tant mieux, mais ça ne change rien au fait que c’est difficilement lisible. Et ton dernier exemple le confirme complètement. Je ne comprend même pas la première ligne.
“Il ne faut pas de o ou de @ ici, tu appelles twice avec comme premier argument twice, ce n’est pas de la composition de fonction ”
J’ai du mal à comprendre cette remarque, c’est justement ça la composition de fonction : si on a f(x) = x² et g = f o f ça donne g(x) = x^4. Ou autrement dit f o f (x) = f ( f(x) ).
Pour le @ c’était parce qu’on a pas de caractère “rond” sur le clavier, et utiliser o à la place n’est pas très propre (après tout o pourrait être une variable par exemple) alors du coup @ serait un bon substitut.
Notons également que dire “on pourrait faire un langage fonctionnel” avec une syntaxe plus claire est vrai, mais au final ça se rapprocherait du Python, la récursion en moins. Il y a une raison pour laquelle les langages fonctionnels les plus populaires ont une syntaxe alambiquée, c’est qu’ils sont conçus pour une certaine manière de penser, à laquelle la syntaxe colle bien. Python étant optimisé pour la lisibilité, et ça se fait au détriment de raccourcis dont les langages fonctionnels sont très friands.
Donc certes, on ne peut pas faire le raccourcis : fonctionnel <=> syntaxe difficile en théorie, mais IRL, ça se vérifie.
Salut !
@Anti-Artymort:
Je pense qu’il y a peu de mauvaise foi dans certains commentaires, en particulier quant au caractère moins lisible du FP pour raisons syntaxiques… Certes la syntaxe d’Ocaml est affreuse (désolé Artymort, mais elle a mal vieillie ) certes la syntaxe de Lisp est (((étonnante))) et certes la syntaxe d’Haskell est parfois un peu trop cabalistique. Cela étant, prenez un snippet C++ qui n’est pas simplement du C+STL et c’est la même chose.
Bref, ne confondons pas syntaxe et lisibilité du fonctionnel. La syntaxe de base de Python est relativement propre, c’est un point sur lequel on s’accordera volontiers, et pour un outsider (comprendre: n’ayant jamais fait de python) c’est effectivement assez simple car proche de l’anglais. MAIS dès que l’on sort du cadre trivial, l’effort intellectuel est le même.
Un point très important qui ajoute en lisibilité du fonctionnel c’est le filtrage: on ne peut pas faire plus expressif, la structure même des données est explicitée sous
les yeux du lecteur !
@ Sam:
Je ne prétends pas défendre Artymort, mais je me dois de répondre aux critiques que tu lui as opposées:
Le qsort est certes académique, mais pas le diviser pour régner. Et dans de tels cas, les langages fonctionnels te permettent des solutions très claires et concises.
Un autre exemple: le backtracking. Le backtracking fonctionnel fait de manière générique avec des continuations est d’une lisibilité à toute épreuve, chose que tu ne pourras jamais obtenir autrement.
Je déjà vu le TP Qsort en fonctionnel … Et cela permet de voir clairement qui a comprit l’algorithme et qui ne l’a pas compris. S’il s’agit d’implémenter, sans faire d’effort profond de compréhension, certes il y aura des erreurs, sinon je partage l’avis d’Artymort, tu ne peux pas pas te planter en l’implémentant:
condition de sortie triviale, filtrage permettant le découpage direct de ta liste sans erreurs d’indices possibles, partitionement qui suit l’intuition mathématique …
C’est véritablement impossible de faire une erreur autre qu’une couille syntaxique.
Coder un parser en fonctionnel peut être fait de manière très propre, il n’y aucun problème la dedans.
Je suis d’accord avec ton dernier point cependant :-)
En outre, mais cela a déjà été dit, l’autre immense avantage du fonctionnel c’est le typage statique. Et ça, c’est un gain de temps en débugage qui est plus qu’immense.
Par contre et là c’est indéniable, si tu dois débuger à la main de la récursion un tantinet non triviale, ca peut rapidement devenir l’enfer.
Question sérieuse: existe-t-il un langage fonctionnel relativement facile à apprendre avec une syntaxe pas toute pourrie comme @TomTom le décrit ? J’aimerais bien comprendre cette histoire de ne pas utiliser d’indice ou d’erreur impossible, et tout simplement essayer un autre type de langage, mais je ne peux pas y passer 2 mois juste pour voir ce que ça donne…
En essayant de comprendre le 0bin de Artymort, j’ai l’impression qu’on peut écrire du code similaire avec numpy: x[x < pivot] ça filtre, la concaténation est plus chiante à acrire parce qu’il faut utiliser des fonctions (mais bon après tout c’est de la syntaxe, ce n’est qu’un détail…)
@kontre
Franchement, de mon point de vue, ça dépend de ce que l’on appelle «langage fonctionnel»…
Le seul que je connaisses à être vraiment complètement purement parfaitement fonctionnel c’est haskell qui n’a aucun effet de bord et est complètement immutable, mais même s’il est assez amusant à apprendre sa syntaxe est… elle demande du temps.
Tout les autres permettent de faire des choses mutables et sont donc grosso modo aussi fonctionnels que python, la seule chose qui les différencie c’est donc quelques éléments de syntaxe et des fonctions adaptées.
Du coup, je pense que je partirais sur python avec une librairie comme fn.py et un bon bouquin sur la programmation fonctionnelle, ou alors peut-être quelque choses comme Scala donc j’ai entendu beaucoup de bien mais que je ne recommanderai pas trop fort faute de l’avoir vraiment utilisé.
Voilà, j’ai repris la même structure du quicksort mais en utilisant python. J’ai même fait une version sans indices pour python 3 mais je dois changer un type derrière alors c’est moins joli.
http://0bin.net/paste/gbEB+Qw7FEzpB2sK#HKNNp4+1PWF0Cnm0OwuqOHMrUO5tnijWQw8NdSrFGIo
à comparer avec http://0bin.net/paste/zCFY0+h2hOYMfjTd#qz+ymGf4Nd6N+Km4RCWVxKmv9+E+cGTPEXB6tqqUlaV
Vous noterez au passage que python m’a appris à utiliser des mots complets pour mes variables pour améliorer la lisibilité. :)
Bon, j’ai relu le commentaire de TomTom…. Honte à moi de n’avoir pas fait mes devoirs plus tôt, mais du coup je me dois d’apporter une correction : Python n’est pas vraiment adapté à la partie «disparition des erreurs» faute de typage statique qui joue une grosse part dans la découverte des erreurs à priori.
Yo,
Je pense que le clash entre Artymort et les autres sur la programmation fonctionnelle (dont je suis un même un ardent fan) vient du fait que vous n’en parlez pas du tout dans le cadre des mêmes usages. Je sais pas si cette phrase était bien française, mais d’un côté Sam dit que “Un dev Web, un sysadmin, un intégrateur, un dev ops ou testeur ou un créateur d’UI est plus à l’aise avec une notation proche d’un langage parlé. Structuré certes, mais plus naturel.”, tandis que Artymort pense à de la “belle” programmation.
Pour venir moi-même de l’impératif (C en gros), c’est vrai que c’est un paradigme qui me paraît beaucoup plus clair quand il s’agit de faire des petits programmes de bidouille, et je me demandais à quoi pouvait bien servir OCaml la première fois que j’ai vu à quoi ça ressemblait. Cependant, aujourd’hui c’est le seul langage dans lequel je programme (avec Coq, mais bon …) mais c’est grâce d’une part à l’enseignement mathématico-logique que j’ai suivi ces dernières années qui me permet de voir la différence entre un langage émergeant d’une vraie réflexion et aux fondements mathématiques solides (OCaml donc, mais j’imagine aussi Haskell, Scheme & cie) et un truc plus bourrin, plus sale parce que pensé pour les applications (genre de l’OS dans le cas de C) et l’utilisateur final, et d’autre part à mes propres besoins en programmation, à savoir principalement de l’analyse de programme (typage et interprétation abstraite).
Bref, 3615 ma vie, mais en gros autant je comprends qu’on sorte python quand on a besoin d’un résultat rapide de combinatoire ou de multiplication de matrice, autant je préfère que les auto-pilotes des avions que je prends soient codés dans un langage qui met en avant la sécurité par rapport à la facilité d’utilisation.
D’ailleurs, pour reprendre la définition de bon code vue dans le post :
“Ca marche.
Ca continue de marcher.
On comprend pourquoi ça marche.” —–> Pour reprendre mon exemple d’auto-pilote, quand un code fait ~10 millions de lignes (potentiellement assez obscures pour cause de logiciel embarqué) je préfère qu’il soit vérifié automatiquement et formellement (ce qui est beaucoup plus simple pour du code fonctionnel, puisque les outils théoriques sont beaucoup plus “sound”. Voire tout simplement existants) plutôt que mon pote Bob le programmeur me dise “Bon ben j’ai passé 3 jours à lire le code entre deux tringlages de Cindy de la compta, franchement ça m’a bien l’air de marcher”.
Bref, faites preuves d’ouverture d’esprit, prenez vous tous la main et chantez ensemble le bonheur qu’est la vie.
PS : Python c’est de la merde
Je vois à peu près ce que tu veux dire. La réponse de Python à “je préfère qu’il soit vérifié automatiquement” c’est qu’on ne peut pas se reposer sur le typage pour vérifier que le programme fait ce qu’il faut, il faut des tests. Or il se trouve que c’est facile d’écrire une suite de tests en python !
Après je ne m’avancerai pas sur la preuve formelle d’un programme, j’en sais tout juste assez pour savoir que j’y connais rien.
J’aime bien le terme “belle programmation”, ça fait tout de suite objectif !
@PeuLeuBeu : les types hints arrivent d’ailleurs pouce genre de système qui clairement étaient avant mieux écrits avec un langage typé.
Bonjour,
je ne vais pas parler sur la FP, mais j ‘aimerai bien que tu nous parles sur le marshal.
@PeuLeuBeu: Haskell est encore plus basé sur des fondements logiques que les autres. D’ailleurs il suffit de regarder ce qu’il y a comme techniques de compilations dans ghc, ça vend du rêve. ( et puis les monades <3)
@Kontre: non ça n’est absolument pas pareil. Faire des tests unitaires te permet de vérifier que ton programme fait ce que tu attends de lui dans des cas d’utilisations que tu as envisagés. Pour des programmes non sensibles ça va. Faire de la preuve de programme c’est immensément plus dur ( l’interpretation abstraite et l’analyse statique c’est bien mais il y a un moment où tu touches à du cambouis dans coq et la c’est coton) mais par contre tu es assure que ton programme fait ce que tu attends de lui dans TOUS les cas.
C’est très différent, et c’est pour ça que les codes sensibles ( avionique, embarqué dans des situations où le contrôle est primordial ( réacteurs de centrale, expérimentations en physique des hautes énergies, … ) sont prouvés et non pas seulement testés. Et comme l’a dit Peuleubeu, il sont souvent écrits en fonctionnels, parce que les outils sont sounds pour les LP car la théorie mathématiques derrière est intimement lié à cela.
Je plussoie à 100% le discours de Peuleubeu (y compris le PS, mais ça c’est juste une affaire de troll et de goût personnel).
Le premier langage que j’ai appris a aussi été C, et encore aujourd’hui, mon extrême nullité fait que je n’arrive pas à écrire des programmes C triviaux sans warning ou bugs. Ocaml en revanche, ça a été love at first sight et aujourd’hui je ne programme quasiment plus qu’en ça. Comme Artymort, je n’ai jamais eu besoin d’y faire une trace d’exécution (de toute façon je ne sais pas comment on fait), cela alors que j’ai déjà codé des trucs non triviaux comme des petits compilateurs, pour répondre à la remarque de Sam je ne sais plus où sur les compilateurs HTML.
Dans un concours de programmation, je me doute que Python doit être un peu plus populaire que le fonctionnel en général car plus productif plus rapidement (quoique ça dépend de qui programme), mais pour du programme sensible comme l’embarqué, faire une preuve de programme, universelle, est plus rassurant que se fier à la batterie de test de Bob, et surtout immensément plus simple en fonctionnel. Comme le dit un certain théorème de Curry-Howard, la notion de démonstration est même équivalente à celle de typage, alors à partir du moment où Python n’est pas typé statiquement c’est forcément moins adapté à du programme sûr (@Kontre, ce qui est différent du programme intensément testé).
Et comme je suis nazi, j’applique ça aussi à mon code de tous les jours, donc Python c’est non tout court pour moi. Encore une fois c’est un choix personnel, je ne dis pas que Python c’est de la merde absolue et que ses utilisateurs devraient tous crever, mais simplement que ce n’est pas adapté à toutes les situations; situations qu’il n’est pas amené à tout le monde de rencontrer selon ce qu’il fait dans la vie, alors gardons l’esprit ouvert.
J’en profite pour remercier le civisme général qui règne dans les commentaires car avec un article qui appelle autant au troll, on aurait pu avoir des débordements. Je suppose qu’on a les lecteurs qu’on mérite :)
Malheureusement suite à cet article, un des auteurs de haskell est mort : https://messages.yale.edu/messages/University/univmsgs/detail/121669
Qu’on soit bien d’accord: je n’ai pas comparé une suite de tests à une preuve formelle de fonctionnement, j’ai comparé un programme qui s’appuie sur le typage statique pour vérifier que ça marche à un programme avec des tests.
Toute cette discussion confirme bien que la programmation fonctionnelle c’est vraiment fait pour les grosses têtes ! Je préfère que ce soient eux qui fassent les avions… Cela dit, les programmes inclus dans les voitures sont apparemment bien buggués eux. Je suppose qu’une tonne de métal lancé à 130 km/h c’est moins sensible.
@PeuLeuBeu si je me trompe pas une partie de l’avionique de l’A380 est codé en C ( :( ) mais vérifié en OCaml ou similaire ( projet ASTRÉE (« Analyseur statique de logiciels temps-réel embarqués ») :) )
@Xavier : T’as tout à fait raison (j’y mettrais pas ma main à couper mais il me semble en effet que analyseurs chez Airbus sont faits en OCaml). Je sais pourquoi c’est fait en C, sans doute un mélange entre l’habitude des programmeurs, les besoins en optimisation et les bibliothèques disponibles, mais malheureusement parfois faut faire face au monde réel :(
En plus ça doit être un truc très généralisé, parce que je vois passer pas mal de travaux de recherche où des mecs prennent des techniques de vérification qui viennent du “monde formel” et les adaptent aux langages du “monde réel”, genre C, C++ (et là ça doit être chaud), Java & cie. Le plus emblématique ça doit être CompCert, le compilateur C certifié développé par Xavier Leroy, le créateur d’OCaml.
@Kontre : Bah si le typage statique et une batterie de tests sont comparables niveau vérification, autant choisir le tapage statique puisque c’est automatique ^^ (pas comme les antibiotiques \o/).
Après il ne faut pas se laisser impressionner par les mots “avionique”, “embarqué” et “Xavier Leroy” (parce qu’on dirait bien que ça commence à partir dans ce sens), le fonctionnel c’est juste une question d’habitude, pas d’intelligence. Je prends pour exemple le post d’Eric plus haut qui enseignait Haskell à des élèves de primaires et que ça passait… Mais certes ce n’est pas facile quand on a pris ses marques dans le mode de vie impératif, comme le contraire est aussi vrai (je n’ai pas la moindre idée de comment écrire un algo d’exploration de labyrinthe sans récursivité par exemple).
Et pour les voitures… Prendre la voiture est de toute façon moins sûr que prendre l’avion en général (4000 morts par an contre 600 en gros), mais pour le coup je suis pas sûr que la faute soit au côté informatique (aujourd’hui du moins). Je suis pas du tout connaisseur, mais je crois que les voitures ont encore une grosse composante mécanique, et que la partie informatisé des appareils est encore récente et donc pas encore totalement sensible. Enfin j’en sais rien en fait. Mais pour le coup je plussoie la remarque, faudra penser à lancer un lobby en partenariat avec l’INRIA pour forcer les constructeurs à tout coder en OCaml et vérifier en Coq (anyone?).
@PeuLeuBeu : Pour Airbus/Ocaml, je suis pas totalement sûr non plus, mais une certaine chercheuse de chez Airbus donne des analyseurs à faire en OCaml en cours à Paris 6…
@Noir : le typage statique n’est pas cas comparable à une batterie tests, cela correspond peut être à 20% des tests à écrire. On monte à 30% avec un langage compilé. Mais même dans ce cas, les tests doivent vérifier : la stabilité de l’API, le comportement selon le passage de différentes valeurs acceptables et inacceptables, la cohérence des erreurs… Et évidement selon le software qu’on fait, des détails comme la taille des choses, le temps que les choses prennent, le nombre d’appels, le format, la création/fermeture d’une ressource, un affichage, etc.
Dév Ruby here. D’accord avec tous les points énoncés dans l’article.
Du côté des Rubyistes, le dernier language fonctionnel à la mode est Elixir. Première impression: sexy. Syntaxe est Ruby-like (au premier abord du moins), basé sur la VM Erlang avec les mêmes perfs. En creusant légèrement le language est aussi plus pragmatique qu’Erlang avec un tooling correct et une stdlib moins craignos (pas le même niveau que Python bien sûr).
C’est pour l’instant le seul language FP que je trouve compatible avec l’esprit de ce post: lisible, compréhensible. Clojure et Lisp me donnent la gerbe avec leurs parenthèses. Scala est trop Javaesque à mon goût et j’ai eu ma dose de Java par le passé. Haskell et pur Erlang ne me disent rien.
Qui sait, si le language a toujours le vent en poupe dans quelques années je switcherai mes APIs dessus.
@Sam : Je suis parfaitement d’accord pour la non équivalence tests-typage statique, c’est juste que le précédent post de Kontre avait l’air de suggérer quelque chose de cet acabit. Je me doute que ce n’était pas le message exact qu’il voulait passer, je répondais sur le ton de l’humour et de l’ironie. Mais 20% c’est déjà bien quand même.
Certes, c’est pour ça que sur des gros projets, je serais le premier à utiliser les (très moches) type hints.
Une proposition pour de la TCO en opt in avec le mot clé return from : http://www.reddit.com/r/Python/comments/33qzzf/what_features_should_python_steal_from_other/cqo7k1n
Sam > dans la communauté Scala, on entend souvent que dès que si ça compile, ça fonctionne et qu’il n’y a pas vraiment besoin des tests du coup :D (bon, lors d’un de mes premiers essais, j’ai eu droit à une exception au runtime à cause d’un type incorrect, comme quoi le typage statique n’est pas si bon que ça)
Ca c’est le genre de philosophie qui me fait bader. Qu’on dise clairement “ok, on est feignasse, alors on va se contenter de la compilation comme niveau de qualité”, ça me va. C’est honnête. Mais dire que y a pas besoin de test, c’est comme dire “la voiture roule et freine, on va pas faire de crash test”.
@Noir Re-re-re-re-reprécision: j’ai comparé typage statique et tests, mais avec comme résultat de la comparaison que les tests c’est mieux. L’exemple typique (aha) c’est ce que dit @matthieu. Pour résumer: typage statique < tests < preuve formelle. Tout le monde est content là ? ^^
@Kontre : Non, désolé pas encore ^^ Je suis d’accord pour :
typage statique seul < tests < preuve formelle
La rigueur imposée dès la compilation par le statique automatise pas mal de tests, mais ça ne fait pas tout on est d’accord : il faut toujours faire plein de tests même en Ocaml, Haskell et Scala. Mais pour moi c’est pas une raison pour se passer du typage statique. Certes un programme C très abondamment testé vaudra en général mieux qu’un programme OCaml qui compile mais n’a jamais été exécuté, mais une fois les tests OCaml passés on arrive au même point; si ce n’est que moins de tests sont nécessaires en général pour la version OCaml qu’elle se porte mieux à une potentielle preuve de programme par la suite (à mon sens en tout cas).
Bon, j’ai légèrement l’impression de me répéter mais à mon avis les tests et le typage c’est pas pour les mêmes usages. Enfin pas pour vérifier les mêmes trucs quoi. Perso j’ai l’impression que les tests c’est pour savoir comment bidouiller les “valeurs” au sein d’un programme, genre comment régler les curseurs, alors que le typage permet de vérifier que la structure d’un programme a un sens.
Enfin dans mon expérience en OCaml, quand je code un truc qui ressemble à peu près à l’algo que j’ai en tête et que ça type, ben je suis à peu près sûr que le code est ok à réglages près.
Bon, je sais pas si je m’exprime bien, z’avez qu’à tous coder en Caml et vous verrez par vous-même bordel.
Vous avez quand même un peu remarqué qu’on cherche la petite bête tout en étant tous d’accord ?
@Noir: Le fait que le typage était seul me paraissait assez évident…
@PeuLeuBeu T’as jamais eu de problème de signe dans tes programmes ? C’est le genre de truc difficile à voir et où le typage ne t’apporte rien (pour les petits malins, dans les calculs on utilise plus souvent des floats que des unsigned int), la seule vérification possible c’est le test (ou la preuve formelle).
J’ai regardé vite fait si ça se faisait le calcul numérique avec des langages fonctionnels (c’est mon domaine, j’y peux rien !). Il y a des librairies existantes en Haskell, OCaml et F#. J’ai vu aussi que certains algos numériques simples ne s’écrivent pas de manière optimale en FP. Par contre la lib FFTW (pour faire des FFTs ultra-rapides) utilise OCaml, tout n’est pas perdu ! ;)
Bien sûr que ça m’est déjà arrivé, et je suis d’accord que c’est le genre de truc qu’on repère avec les tests. Un moins à la place d’un plus, moralement je trouve que c’est comme un 4 à la place d’un 3 quelque part, c’est à dire vraiment de l’ajustement plutôt que de la structure de code.
Wow, je n’entrerais pas dans le débat, mais wow. Un super article, que je vais faire lire. A pleins de gens. Continuez comme ça vous êtes formidables.
Nb: Guido van Rossum. Sans V majuscule. Cf sa bio twitter.
@PeuLeuBeu Appelle-le comme tu veux, t’auras au final pas le bon résultat et paf l’avion au final ! Que ce soit de la belle programmation ou non :)
@dineptus Bien sur qu’il faut une formation specifique, tout autant que pour lire du code python.
@Sam “La charge cognitive” est une notion tres relative.
Si l’on compare par exemple l’anglais et le japonais:
26 lettres en anglais contre environ 2000 caracteres en japonais
des mots espaces en anglais, aucun espace en japonais
Est ce que ca veut dire que le japonais a une charge cognitive plus forte? Je ne pense pas, les japonais lisent aussi vite et sans efforts particuliers. La charge est juste repartie differement.
Pour les langages fonctionnels je pense que c’est un peu la meme idee, c’est juste une facon differente d’apprehender le code.
Apres, je comprends mal cette peur de la recursion, si la fonction est pure elle peut etre testee independament (et prouvee comme logiquement correcte avec un peu d’effort). Et la plupart du temps une solution basee sur fold ou map suffisent.
Pour apporter deux trois choses au debats tests / typage:
Le typage statique et les monades dans haskell apportent une securite dans le code que peu d’autres langages ont. Sur le sujet A Pragmatic Case for Static Typing with Brian Hurt est vraiment interessante.
Les langages fonctionels peuvent etre teste avec des tests de proprietes qui permettent de trouver des bugs plutot bien caches via generation de tests automatiques. Sur le sujet Testing for the Unexpected de Ulf Wiger et Testing the Hard Stuff and Staying Sane de John Hughes.
Elixir c’est pas mal et ca apporte de vrais choses en plus a erlang comme une bonne gestion de l’unicode et mix.
Dans un autre genre, il y a Elm qui est base sur haskell et qui permet de creer des applis web/js.
J’ai enseigné le PHP, le Java, le Python et le Javascript. J’ai vu enseigné le VM, le lisp, l’erlang et le C. Le temps et l’effort de la formation est toujours bien plus faible en Python.
Oui, la charge cognitive est plus importante en japonais.
Ce n’est pas un hasard si les japonais favorisent les personnages très stéréotypés, les expressions exagérées et les onomatopées dans les arts. Cela donne un contexte important pour la compréhension d’un message.
Prend un sociologue, et apprends lui la programmation. Et tu comprendras.
Après, je ne retire en aucun cas les mérites des mécanismes des langages que tu cites. Je dis simplement qu’ils se font au prix d’autres qualités, et que souvent, je donne la priorité à ces dernières. Et Guido aussi.
@Sam : hum alors… Je ne prononcerai pas sur la “charge cognitive” du japonais parce que je n’y connais rien (à la charge cognitive, pas au japonais), mais pour l’argument sur les stéréotypes… On m’a appris quand j’étais au lycée que la commedia dell’arte était bien connue pour ses personnages volontairement stéréotypés, idem en France avec le théâtre de Molière, ou même dans certaines pièces de la Grèce antique (arrêtez-moi si je dis de la merde, mes cours de français commencent un peu à dater). Pourtant l’italien et le grec (et le japonais si on enlève les caractères chinois), pour les rudiments que j’en ai c’est nettement plus facile à apprendre et à lire que le français. En bref, à mon sens, les onomatopées et les stéréotypes ce ne sont corrélées à la difficulté de la langue sous-jacente.
Après je ne me positionne pas sur les la prolongation aux langages de prog, comme le dit Eric je trouve ça très subjectif; le sens de codes en Haskell/Ocaml m’apparaissent beaucoup plus clairement que ceux en C ou même en Python (no joke), mais j’ai bien vu que masse de gens qui ont publié ici ne sont pas de mon avis.
Je pense que si on a un background de maths et qu’on fait de l’algo, Haskell doit effectivement être très parlant, son expressivité étant optimisé pour ça.
Le débat a repris sur python-ideas, avec la suggestion d’utiliser @ pour faire de la composition de fonction : http://code.activestate.com/lists/python-ideas/33320/
Ca ne passera jamais sous cette forme, heureusement. Mais pour ceux qui veulent s’y essayer, c’est facile à implémenter:
Perso, quand je vois ce genre de code, je pleure. Tout devient plus dur à debugger : pdb, la stack trace, les prints, tu peux tout te mettre au cul.