En programmation fonctionnelle, étant donné que presque toutes les structures de données sont immuables, lorsque l’état doit changer, une nouvelle structure est créée. Est-ce que cela signifie beaucoup plus d'utilisation de la mémoire? Je connais bien le paradigme de la programmation orientée objet, maintenant j'essaie de mieux comprendre le paradigme de la programmation fonctionnelle. Le concept de tout être immuable me confond. Il semblerait qu'un programme utilisant des structures immuables nécessite beaucoup plus de mémoire qu'un programme avec des structures mutables. Est-ce que je regarde ça de la bonne façon?
functional-programming
Jbemmz
la source
la source
Réponses:
La seule réponse correcte à cette question est "parfois". Les langages fonctionnels peuvent utiliser beaucoup d’astuces pour éviter le gaspillage de mémoire. L'immuabilité facilite le partage des données entre les fonctions, et même entre les structures de données, car le compilateur peut garantir que les données ne seront pas modifiées. Les langages fonctionnels ont tendance à encourager l'utilisation de structures de données pouvant être utilisées efficacement en tant que structures immuables (par exemple, des arbres au lieu de tables de hachage). Si vous ajoutez de la paresse dans le mélange, comme le font de nombreux langages fonctionnels, cela ajoute de nouvelles façons d'économiser de la mémoire (cela ajoute également de nouvelles façons de gaspiller de la mémoire, mais je ne vais pas entrer dans cela).
la source
Cela dépend de la structure des données, des modifications exactes effectuées et, dans certains cas, de l'optimiseur. A titre d'exemple, considérons l'ajout au début d'une liste:
Ici, les besoins en mémoire supplémentaires sont constants, de même que le coût d’appel à l’exécution
prepend
. Pourquoi? Parceprepend
que crée simplement une nouvelle cellule qui a42
pour tête etlist1
pour queue. Pour ce faire, il n'est pas nécessaire de copier ou d'itérer autrementlist2
. Autrement dit, à l'exception de la mémoire requise pour le stockage42
,list2
réutilise la même mémoire que celle utilisée parlist1
. Les deux listes étant immuables, ce partage est parfaitement sécurisé.De même, lorsque vous travaillez avec des structures arborescentes équilibrées, la plupart des opérations ne nécessitent qu'une quantité logarithmique d'espace supplémentaire car tout, à l'exception d'un seul chemin, peut être partagé.
Pour les tableaux, la situation est un peu différente. C'est pourquoi, dans de nombreuses langues de PF, les tableaux ne sont pas utilisés couramment. Cependant, si vous faites quelque chose comme
arr2 = map(f, arr1)
et que vousarr1
ne l'utilisez plus après cette ligne, un optimiseur intelligent peut en réalité créer du code qui mutearr1
au lieu de créer un nouveau tableau (sans affecter le comportement du programme). Dans ce cas, la performance sera comme dans une langue impérative bien sûr.la source
Les implémentations naïves exposeraient effectivement ce problème: lorsque vous créez une nouvelle structure de données au lieu de mettre à jour une structure existante sur place, vous devez supporter un temps système supplémentaire.
Différentes langues ont différentes façons de gérer cela, et la plupart d'entre elles utilisent quelques astuces.
Une stratégie est la collecte des ordures . À partir du moment où la nouvelle structure a été créée ou peu de temps après, les références à l’ancienne structure sortent du domaine, et le ramasse-miettes la détecte instantanément ou assez tôt, en fonction de l’algorithme du GC. Cela signifie que, même s'il y a toujours une surcharge, celle-ci n'est que temporaire et ne croîtra pas de manière linéaire avec la quantité de données.
Un autre choix consiste à sélectionner différents types de structures de données. Lorsque les tableaux constituent la structure de données de liste idéale dans les langages impératifs (généralement enveloppés dans une sorte de conteneur de réallocation dynamique, comme
std::vector
en C ++), les langages fonctionnels préfèrent souvent les listes chaînées. Avec une liste chaînée, une opération de préposition ('inconvénients') peut réutiliser la liste existante en tant que fin de la nouvelle liste, de sorte que tout ce qui est réellement alloué est la nouvelle tête de liste. Des stratégies similaires existent pour d'autres types de structures de données - ensembles, arbres, vous l'appelez.Et puis il y a l'évaluation paresseuse, à la Haskell. L'idée est que les structures de données que vous créez ne sont pas entièrement créées immédiatement. au lieu de cela, ils sont stockés en tant que "thunks" (vous pouvez les considérer comme des recettes permettant de construire la valeur lorsque cela est nécessaire). Ce n'est que lorsque la valeur est nécessaire que le thunk est développé en une valeur réelle. Cela signifie que l’allocation de mémoire peut être différée jusqu’à ce que l’évaluation soit nécessaire. À ce stade, plusieurs thunks peuvent être combinés dans une allocation de mémoire.
la source
Je connais seulement un peu Clojure et ses structures de données immuables .
Graphiquement, nous pouvons représenter quelque chose comme ceci:
la source
Outre ce qui a été dit dans d’autres réponses, je voudrais mentionner le langage de programmation Clean, qui prend en charge les types dits uniques . Je ne connais pas ce langage, mais je suppose que des types uniques prennent en charge une sorte de "mise à jour destructive".
En d'autres termes, alors que la sémantique de la mise à jour d'un état est que vous créez une nouvelle valeur à partir d'une ancienne en appliquant une fonction, la contrainte d'unicité peut permettre au compilateur de réutiliser des objets de données en interne car il sait que l'ancienne valeur ne sera pas référencée. plus dans le programme après que la nouvelle valeur a été produite.
Pour plus de détails, voir par exemple la page d'accueil de Clean et cet article de Wikipédia.
la source