Le fait d'avoir des variables locales mutables dans une fonction qui ne sont utilisées qu'en interne (par exemple, la fonction n'a pas d'effets secondaires, du moins pas intentionnellement) est-il toujours considéré comme "non fonctionnel"?
Par exemple, dans la vérification de style de cours "Programmation fonctionnelle avec Scala", toute var
utilisation est considérée comme mauvaise
Ma question, si la fonction n'a pas d'effets secondaires, est-elle toujours déconseillée d'écrire du code de style impératif?
Par exemple, au lieu d'utiliser la récursivité de queue avec le modèle d'accumulateur, qu'est-ce qui ne va pas avec faire une boucle for locale et créer une mutable localeListBuffer
et l'ajouter, tant que l'entrée n'est pas modifiée?
Si la réponse est "oui, ils sont toujours découragés, même s'il n'y a pas d'effets secondaires", alors quelle est la raison?
la source
var
est toujours non fonctionnel. Scala a des valeurs paresseuses et une optimisation de la récursivité de la queue, ce qui permet d'éviter complètement les variables.Réponses:
La seule chose qui est une mauvaise pratique sans équivoque ici est de prétendre que quelque chose est une fonction pure quand il ne l'est pas.
Si les variables mutables sont utilisées d'une manière qui est vraiment et complètement autonome, la fonction est purement externe et tout le monde est content. Haskell le supporte en fait explicitement , le système de types garantissant même que les références mutables ne peuvent pas être utilisées en dehors de la fonction qui les crée.
Cela dit, je pense que parler des "effets secondaires" n'est pas la meilleure façon de voir les choses (et c'est pourquoi j'ai dit "pur" ci-dessus). Tout ce qui crée une dépendance entre la fonction et l'état externe rend les choses plus difficiles à raisonner, et cela inclut des choses comme connaître l'heure actuelle ou utiliser un état mutable caché d'une manière non thread-safe.
la source
Le problème n'est pas la mutabilité en soi, c'est un manque de transparence référentielle.
Une chose référentiellement transparente et une référence à celle-ci doivent toujours être égales, donc une fonction référentiellement transparente retournera toujours les mêmes résultats pour un ensemble donné d'entrées et une "variable" référentiellement transparente est vraiment une valeur plutôt qu'une variable, car elle ne peut pas changer. Vous pouvez créer une fonction référentiellement transparente qui contient une variable mutable; ce n'est pas un problème. Il peut cependant être plus difficile de garantir que la fonction est référentiellement transparente, selon ce que vous faites.
Il y a un cas où je peux penser où la mutabilité doit être utilisée pour faire quelque chose de très fonctionnel: la mémorisation. La mémorisation consiste à mettre en cache les valeurs d'une fonction, elles ne doivent donc pas être recalculées; il est référentiellement transparent, mais il utilise une mutation.
Mais en général, la transparence référentielle et l'immuabilité vont de pair, à part une variable locale mutable dans une fonction et une mémorisation référentiellement transparente, je ne suis pas sûr qu'il existe d'autres exemples où ce n'est pas le cas.
la source
Ce n'est pas vraiment bon de résumer cela en "bonne pratique" vs "mauvaise pratique". Scala prend en charge les valeurs mutables car elles résolvent certains problèmes bien mieux que les valeurs immuables, à savoir celles qui sont de nature itérative.
Pour la perspective, je suis assez sûr que via
CanBuildFrom
presque toutes les structures immuables fournies par scala font une sorte de mutation en interne. Le fait est que ce qu'ils exposent est immuable. Garder autant de valeurs immuables que possible permet de rendre le programme plus facile à raisonner et moins sujet aux erreurs .Cela ne signifie pas que vous devez nécessairement éviter les structures et valeurs mutables en interne lorsque vous rencontrez un problème mieux adapté à la mutabilité.
Dans cet esprit, de nombreux problèmes qui nécessitent généralement des variables modifiables (telles que la boucle) peuvent être mieux résolus avec de nombreuses fonctions d'ordre supérieur fournies par des langages comme Scala (carte / filtre / pli). Soyez conscient de ceux-ci.
la source
map
,filter
,foldLeft
EtforEach
faire l'affaire la plupart du temps, mais quand ils ne le font pas, être capable de sentir que je suis « OK » pour revenir à la force brute du code impératif est agréable. (tant qu'il n'y a pas d'effets secondaires bien sûr)Mis à part les problèmes potentiels avec la sécurité des threads, vous perdez également généralement beaucoup de sécurité de type. Les boucles impératives ont un type de retour
Unit
et peuvent prendre à peu près n'importe quelle expression pour les entrées. Les fonctions d'ordre supérieur et même la récursivité ont une sémantique et des types beaucoup plus précis.Vous avez également beaucoup plus d'options pour le traitement des conteneurs fonctionnels qu'avec les boucles impératives. Avec impératif, vous avez essentiellement
for
,while
et des variations mineures sur ces deux commedo...while
etforeach
.Dans fonctionnel, vous avez agréger, compter, filtrer, trouver, flatMap, fold, groupBy, lastIndexWhere, map, maxBy, minBy, partition, scan, sortBy, sortWith, span et takeWhile, pour n'en nommer que quelques-uns plus courants de Scala. bibliothèque standard. Lorsque vous vous habituez à les avoir, les
for
boucles impératives semblent trop basiques en comparaison.La seule vraie raison d'utiliser la mutabilité locale est très occasionnellement pour les performances.
la source
Je dirais que c'est généralement correct. De plus, générer des structures de cette manière peut être un bon moyen d'améliorer les performances dans certains cas. Clojure a résolu ce problème en fournissant des structures de données transitoires .
L'idée de base est de permettre des mutations locales dans une portée limitée, puis de geler la structure avant de la renvoyer. De cette façon, votre utilisateur peut toujours raisonner sur votre code comme s'il était pur, mais vous pouvez effectuer des transformations sur place lorsque vous en avez besoin.
Comme le dit le lien:
la source
Le fait de ne pas avoir de variables locales modifiables présente un avantage: cela rend la fonction plus conviviale pour les threads.
J'ai été brûlé par une telle variable locale (pas dans mon code, ni la source), provoquant une corruption de données à faible probabilité. La sécurité des fils n'a pas été mentionnée d'une manière ou d'une autre, aucun état n'a persisté pendant les appels et il n'y a eu aucun effet secondaire. Il ne m'est pas venu à l'esprit qu'il pourrait ne pas être sûr pour les threads, chasser une corruption de données aléatoire de 1 sur 100 000 est une douleur royale.
la source