Case: Je travaille dans une entreprise, j'écris une application en Python qui gère beaucoup de données dans des tableaux. Je suis le seul développeur de ce programme pour le moment, mais il sera probablement utilisé/modifié/étendu à l'avenir (1-3 ans) par un autre programmeur, à ce moment que je ne connais pas. Je ne serai probablement pas là directement pour aider alors, mais peut-être en donner support par e-mail si j'ai le temps.
Donc, en tant que développeur qui a appris la programmation fonctionnelle (Haskell), j'ai tendance à résoudre, par exemple, le filtrage comme ceci:
filtered = filter(lambda item: included(item.time, dur), measures)
Le reste du code est OO, c'est juste quelques petits cas où je veux le résoudre comme ça, car c'est beaucoup plus simple et plus beau selon moi.
Question: Est-ce que ça va aujourd'hui d'écrire du code comme ça?
Dois-je écrire de la documentation comme expliquer à un enfant ce que fait la ligne?
# Filter out the items from measures for which included(item.time, dur) != True
J'ai demandé à mon patron, et il a juste dit "FP est de la magie noire, mais si cela fonctionne et est la solution la plus efficace, alors c'est OK de l'utiliser."
Quelle est ton opinion à ce propos? En tant que programmeur non-FP, comment réagissez-vous au code? Le code est-il "googable" pour que vous puissiez comprendre ce qu'il fait? J'adorerais avoir des commentaires à ce sujet.
Est-il lisible?
Pour moi: Oui, mais j'ai compris que la communauté Python semble souvent considérer les compréhensions de listes comme une solution plus propre que d'utiliser map()
/filter()
.
En fait, GvR a même considéré la suppression totale de ces fonctions.
Considère ceci:
filtered = [item for item in measures if included(item.time, dur)]
En outre, cela présente l'avantage qu'une compréhension de liste renvoie toujours une liste. map()
et filter()
d'autre part retournera un itérateur dans Python 3.
Remarque: Si vous souhaitez avoir un itérateur à la place, c'est aussi simple que de remplacer []
Par ()
:
filtered = (item for item in measures if included(item.time, dur))
Pour être honnête, je vois peu ou pas de raison d'utiliser map()
ou filter()
en Python.
Est-il modifiable?
Oui, certes, cependant, il y a une chose pour rendre cela plus facile: en faire une fonction, pas un lambda.
def is_included(item):
return included(item.time, dur)
filtered = filter(is_included, measures)
filtered = [item for item in measures if is_included(item)]
Si votre état devient plus complexe, cela évoluera beaucoup plus facilement, cela vous permettra également de réutiliser votre chèque. (Notez que vous pouvez créer des fonctions à l'intérieur d'autres fonctions, cela peut le garder plus près de l'endroit où il est utilisé.)
Comment un développeur qui n'a pas écrit/appris FP réagit-il sur un code comme celui-ci?
Il google pour la documentation Python et sait comment cela fonctionne cinq minutes plus tard. Sinon, il ne devrait pas programmer en Python.
map()
et filter()
sont extrêmement simples. Ce n'est pas comme si vous leur demandiez de comprendre les monades. C'est pourquoi je ne pense pas que vous ayez besoin d'écrire de tels commentaires. Utilisez de bons noms de variables et de fonctions, le code est presque explicite. Vous ne pouvez pas prévoir les fonctionnalités linguistiques qu'un développeur ne connaît pas. Pour tout ce que vous savez, le prochain développeur peut ne pas savoir ce qu'est un dictionnaire.
Ce que nous ne comprenons pas n'est généralement pas lisible pour nous. Ainsi, vous pourriez dire qu'elle n'est pas moins lisible qu'une compréhension de liste si vous n'en avez jamais vu auparavant. Mais comme Joshua l'a mentionné dans son commentaire, je pense moi aussi qu'il est important d'être cohérent avec ce que les autres développeurs utilisent - du moins si l'alternative n'offre aucun avantage substantiel.
Étant donné que la communauté des développeurs regagne de l'intérêt pour la programmation fonctionnelle, il n'est pas rare de voir une programmation fonctionnelle dans des langages qui étaient à l'origine entièrement orientés objet. Un bon exemple est C #, où les types anonymes et les expressions lambda permettent d'être beaucoup plus courts et expressifs grâce à la programmation fonctionnelle.
Cela dit, la programmation fonctionnelle est bizarre pour les débutants. Par exemple, lorsque lors d'un cours de formation, j'ai expliqué aux débutants comment améliorer leur code grâce à une programmation fonctionnelle en C #, certains n'étaient pas convaincus et certains ont dit que le code d'origine était plus facile à comprendre pour eux. L'exemple que je leur ai donné était le suivant:
Code avant refactoring:
var categorizedProducts = new Dictionary<string, List<Product>>();
// Get only enabled products, filtering the disabled ones, and group them by categories.
foreach (var product in this.Data.Products)
{
if (product.IsEnabled)
{
if (!categorizedProducts.ContainsKey(product.Category))
{
// The category is missing. Create one.
categorizedProducts.Add(product.Category, new List<Product>());
}
categorizedProducts[product.Category].Add(product);
}
}
// Walk through the categories.
foreach (var productsInCategory in categorizedProducts)
{
var minimumPrice = double.MaxValue;
var maximumPrice = double.MinValue;
// Walk through the products in a category to search for the maximum and minimum prices.
foreach (var product in productsInCategory.Value)
{
if (product.Price < minimumPrice)
{
minimumPrice = product.Price;
}
if (product.Price > maximumPrice)
{
maximumPrice = product.Price;
}
}
yield return new PricesPerCategory(category: productsInCategory.Key, minimum: minimumPrice, maximum: maximumPrice);
}
Même code après refactoring en utilisant FP:
return this.Data.Products
.Where(product => product.IsEnabled)
.GroupBy(product => product.Category)
.Select(productsInCategory => new PricesPerCategory(
category: productsInCategory.Key,
minimum: productsInCategory.Value.Min(product => product.Price),
maximum: productsInCategory.Value.Max(product => product.Price))
);
Cela me fait penser que:
vous ne devriez pas vous inquiéter si vous savez que le prochain développeur qui maintiendra votre code aura une expérience globale suffisante et une certaine connaissance de la programmation fonctionnelle, mais:
sinon, évitez la programmation fonctionnelle ou mettez un commentaire détaillé expliquant à la fois la syntaxe, les avantages et les mises en garde possibles de votre approche par rapport à une programmation non fonctionnelle.
Je suis un programmeur non-FP et récemment je devais modifier le code de mon collègue en JavaScript. Il y avait une demande Http avec un rappel qui ressemblait beaucoup à la déclaration incluse par vous. Je dois dire qu'il m'a fallu un certain temps (comme une demi-heure) pour tout comprendre (le code dans l'ensemble n'était pas très gros).
Il n'y a eu aucun commentaire et je pense qu'il n'y en avait pas besoin. Je n'ai même pas demandé à mon collègue de m'aider à comprendre son code.
Étant donné que je travaille depuis environ 1,5 an, je pense que la plupart des programmeurs seront en mesure de comprendre et de modifier un tel code, comme je l'ai fait.
En outre, comme l'a dit Joachim Sauer dans son commentaire, il y a souvent des morceaux de FP dans de nombreux langages, comme C # (indexOf, par exemple). Donc, de nombreux programmeurs non-FP traitent cela assez souvent, et l'extrait de code que vous avez inclus n'est pas quelque chose de terrible ou d'incompréhensible.
Je dirais certainement oui!
Il existe de nombreux aspects de la programmation fonctionnelle et l'utilisation de fonctions d'ordre supérieur, comme dans votre exemple, n'en est qu'un.
Par exemple, je considère que l'écriture fonctions pures est extrêmement importante pour tout logiciel écrit dans n'importe quelle langue (où par "pur" je veux dire pas d'effets secondaires), car:
J'évite aussi souvent de muter les valeurs et les variables - un autre concept emprunté à FP.
Ces deux techniques fonctionnent bien dans Python et d'autres langages qui ne sont généralement pas classés comme fonctionnels. Ils sont souvent même pris en charge par le langage lui-même (c'est-à-dire final
variables en Java) Ainsi, les futurs programmeurs de maintenance ne seront pas confrontés à une énorme barrière pour comprendre le code.
Nous avons eu cette même discussion sur une entreprise pour laquelle j'ai travaillé l'année dernière.
La discussion a porté sur le "code magique" et s'il devait être encouragé ou non. En y regardant un peu plus, il semblait que les gens avaient des opinions très différentes sur ce qui était en fait du "code magique". Ceux qui ont soulevé la discussion semblaient surtout signifier que les expressions (en PHP) qui utilisaient le style fonctionnel étaient du "code magique" alors que les développeurs qui provenaient d'autres langages utilisaient plus de style FP dans leur le code semble penser que le code magique était plutôt lorsque vous avez fait l'inclusion dynamique de fichiers via des noms de fichiers et ainsi de suite.
Nous ne sommes jamais parvenus à une bonne conclusion à ce sujet, plus que les gens pensent que le code qui ne semble pas familier est "magique" ou difficile à lire. Est-ce donc une bonne idée d'éviter le code qui ne semble pas familier aux autres utilisateurs? Je pense que cela dépend de l'endroit où il est utilisé. Je m'abstiendrais d'utiliser des expressions de style fp (inclusion dynamique de fichiers, etc.) dans une méthode principale (ou d'importantes parties centrales des applications) où les données devraient être tunnelées de manière claire et facile à lire, intuitive. Par contre, je ne pense pas qu'il faille avoir peur de pousser l'enveloppe, les autres développeurs apprendront probablement FP rapidement s'ils sont confrontés à FP code et peut-être avoir une bonne ressource interne à consulter sur les questions.
TL; DR: éviter dans la partie centrale de haut niveau des applications (qui doivent être lues pour un aperçu des fonctionnalités de l'application). Sinon, utilisez-le.
La communauté C++ a également récemment obtenu des lambda, et je pense qu'ils ont à peu près la même question. La réponse n'est peut-être pas la même, cependant. L'équivalent C++ serait:
std::copy_if(measures.begin(), measures.end(), inserter(filter),
[dur](Item i) { return included(i, dur) } );
Maintenant std::copy
n'est pas nouveau et le _if
les variantes ne sont pas nouvelles non plus, mais le lambda l'est. Pourtant, il est défini assez clairement dans son contexte: dur
est capturé et donc constant, Item i
varie dans la boucle, et la seule instruction return
fait tout le travail.
Cela semble acceptable pour de nombreux développeurs C++. Je n'ai pas échantillonné d'opinions sur les lambda d'ordre supérieur, cependant, et je m'attendrais à beaucoup moins d'acceptation.
Postez un extrait de code à un autre développeur qui ne parle pas aussi bien le python, et demandez-lui s'il peut passer 5 minutes à vérifier le code pour voir s'il le comprend.
Si oui, vous êtes probablement prêt à partir. Sinon, vous devriez essayer de le rendre plus clair.
Votre collègue pourrait-il être stupide et ne pas comprendre quelque chose qui devrait être évident? Oui, mais vous devez toujours programmer conformément à KISS.
Peut-être que votre code est plus efficace/beau/élégant qu'une approche plus simple et à l'épreuve des idiots? Ensuite, vous devez vous demander: dois-je faire cela? Encore une fois, si la réponse est non, alors ne le faites pas!
Si après tout cela, vous pensez toujours que vous avez besoin et que vous voulez le faire de la manière FP, alors faites-le par tous les moyens. Faites confiance à votre instinct, ils sont mieux adaptés à vos besoins que la plupart des gens sur n'importe quel forum :)