Je viens de recevoir ma copie de expert f # 2.0 em> et est tombé sur cette déclaration, qui m'a quelque peu surpris: p>
Par exemple, si nécessaire, vous pouvez
Utiliser des effets secondaires sur les données privées
structures allouées au début de
un algorithme puis jeter ces
structures de données avant de retourner un
résultat; Le résultat global est alors
efficacement un effet secondaire sans effet
fonction. Un exemple de séparation
de la bibliothèque F # est la bibliothèque
implémentation de liste.MAP, qui utilise
mutation en interne; les écritures se produisent
Sur une donnée interne séparée
Structure qu'aucun autre code ne peut
accès. p>
blockQuote>
Maintenant, évidemment l'avantage de cette approche est la performance. Je suis juste curieux s'il y a des inconvénients - faire des pièges pouvant venir avec des effets secondaires s'appliquer ici? Est la parallélisibilité affectée? P>
En d'autres termes, si les performances ont été réservées, il serait préférable d'implémenter (évidemment, cela traite de F # en particulier, mais je suis aussi curieux de la philosophie générale) p>
list.map code> d'une manière pure? p>
7 Réponses :
Il n'affectera pas si la fonction peut être exécutée en parallèle avec d'autres fonctions. Cela affectera si les internes de la fonction peuvent être faits parallèles, mais il est peu probable qu'il s'agisse d'un problème pour la plupart des petites fonctions (telles que la carte), ciblant un PC. P>
J'ai remarqué que certains de bons programmeurs F # (sur le Web et dans les livres) semblent être très détendus pour utiliser des techniques impératives pour les boucles. Ils semblent préférer une boucle simple, avec des variables de boucle mutable, à une fonction récursive complexe. P>
Vous pourriez être intéressé par Simon Peyton Jones's "Fils de l'état fonctionnel paresseux" . Je n'ai jamais fait que par les premières pages, qui sont très claires (je suis sûr que le reste est très clair aussi). P>
Le point important est que lorsque vous utilisez contrôler.monad.st code> pour faire ce genre de chose à Haskell, le système de type lui-même applique l'encapsulation. À Scala (et probablement F #), l'approche est plus "Faire confiance à nous que nous ne faisons rien de sournois ici avec ce
listbuffer code> dans votre carte
" / code> ". P >
En pratique, les effets secondaires incontrôlés sont cependant dans le code de Haskell réel.
@Jonharrop, pouvez-vous donner un exemple?
@sebleblanc: Bien sûr. J'ai étudié plusieurs morceaux de code Haskell open source tel que le gibier frag et trouvé qu'ils avaient eu recours à Unsafeperformio Code> beaucoup. J'ai demandé aux auteurs pourquoi et ils ont tous dit "performance". Depuis, j'ai depuis entendu dire que les problèmes sous-jacents ont été abordés et que cela n'est plus nécessaire, il ne peut plus être le cas.
Pour l'enregistrement, je n'ai pas pu trouver une seule occurrence de UNSAPERFORMIO code> dans toutes les versions publiées de frag.
Encore une fois, pouvez-vous sauvegarder vos revendications?
Si une fonction utilise des structures de données mutables locales, privées (à la fonction), la parallélisation n'est pas affectée. Donc, si la fonction code> code> crée en interne crée une matrice de la taille de la liste et iTère sur ses éléments remplissant le tableau, vous pouvez toujours exécuter D'autre part Si une fonction utilise des structures de données mutables globales, la parallélisation pourrait être affectée. Par exemple, disons que vous avez une fonction code> Memoize code>. Évidemment, tout cela a pour but de maintenir un État global (bien que "Global" en ce sens que ce ne soit pas local à l'appel de la fonction, il est toujours "privé" en ce sens qu'il n'est pas accessible en dehors de la fonction) pour que Il ne doit pas nécessairement exécuter une fonction plusieurs fois avec les mêmes arguments, mais elle est toujours pure car les mêmes entrées produiront toujours les mêmes sorties. Si votre structure de données de cache est thread-coffre-fort (comme Je devrais ajouter que c'est une technique commune en F # pour commencer par une routine purement fonctionnelle, puis l'optimise en tirant parti de l'état mutable (par exemple, la mise en cache, la boucle explicite) lorsque le profilage montre qu'il est trop lent. P > carte code> 100 fois simultanément sur le même Liste et ne vous inquiétez pas car chaque instance de
carte code> aura sa propre matrice privée. Étant donné que votre code ne peut pas voir le contenu de la matrice tant qu'elle n'a pas été renseignée, elle est effectivement pure (rappelez-vous, à un niveau, votre ordinateur doit muter l'état de RAM). P>
ConcurrentComment code>), vous pouvez toujours exécuter votre fonction en parallèle avec elle-même. Sinon, vous pourriez alors faire valoir que la fonction n'est pas pure car elle a des effets secondaires observables lorsqu'il est exécuté simultanément. P>
En fait, c'est une idée fausse commune. Les tripes impures ont tendance à améliorer l'évolutivité car la mutation continue signifie une meilleure réutilisation de l'espace et, par conséquent, moins une allocation et une meilleure empreinte mémoire qui signifie moins de contention à la poubelle et à la mémoire principale, respectivement. Moins de contentions pour les ressources partagées signifie une meilleure évolutivité.
La même approche peut être trouvée dans le clojure. Les structures de données immuables dans la liste des clojures, la carte et le vecteur - ont leurs homologues "transitoires" qui sont mutables. Le RÉFÉRENCE DE CLOJURE À PROPOS DE TRANSIENT Saviguez uniquement pour les utiliser uniquement dans le code qui ne peut pas être vu par «aucun autre code». p>
Il y a des gardes contre les fuites de transitoires dans le code client: p>
La fonction habituelle qui fonctionnent sur les structures de données immuables ne fonctionnent pas sur les transitoires. Les appeleront une exception. P> li>
Les transitoires sont liés au fil qu'ils sont créés. Les modifier à partir de tout autre thread vont lancer une exception. P> LI> ul>
Le code Clojure.core lui-même utilise beaucoup de transitoires derrière les scènes. P>
Le principal avantage de l'utilisation de transitoires est la rapidité massive qu'ils fournissent. P>
de sorte que l'utilisation étroitement contrôlée de l'état mutable semble être ok dans les langues fonctionnelles. P>
Un problème peut être qu'un bon compilateur fonctionnel est construit pour optimiser le code "fonctionnel", mais si vous utilisez des éléments mutables, le compilateur peut ne pas optimiser aussi bon que dans l'autre cas. Dans le pire des cas, cela conduit à des algorithmes plus inefficaces alors la variante immuable. P>
L'autre question que je peux penser est la paresse - une structure de données mutable n'est généralement pas paresseuse, une onction mutable peut donc forcer une évaluation inépcidente des arguments. P>
Je pense que presque tous les inconvénients des effets secondaires sont liés à "interaction avec d'autres parties du programme". Les effets secondaires eux-mêmes ne sont pas mauvais (comme @gabe dit, même un programme fonctionnel pur se mutartons constamment de RAM), ce sont les conséquences vocales des effets (interactions non locales) qui causent des problèmes (avec le débogage / la performance / la compréhension /etc.). SO Effets sur l'état purement local (par exemple sur une variable locale qui ne s'échappe pas) va bien. p>
(Le seul détriment que je puisse penser, c'est que, quand un humain voit un tel mutable local, ils doivent raisonner si cela peut s'échapper. Dans F #, les mutailles locales ne peuvent jamais s'échapper (les fermetures ne peuvent pas capturer des mutables), donc le Seul potentiel «taxe mental» provient du raisonnement sur les types de référence mutable.) P>
résumé: il convient d'utiliser des effets, tant qu'il est simple de vous convaincre que les effets ne se produisent que sur les locaux non échappés. (Il est également correct d'utiliser des effets dans d'autres cas, mais j'ignore ces autres cas, car sur cette question - nous sommes les programmeurs fonctionnels éclairés qui tentent d'éviter des effets chaque fois que c'est raisonnable. :)) p>
(Si vous voulez aller très profondément, des effets locaux, comme ceux de la liste de la liste F #.MAP, ne sont pas seulement un non-obstacle à la parallélysabilité, mais en fait un avantage, du point de vue que plus La mise en œuvre efficace alloue moins, et est donc moins de souche sur la ressource partagée du GC.) P>
RE: Les effets locaux peuvent être plus efficaces. Fuzxxl souligne ci-dessous que le code purement fonctionnel peut être plus facilement optimisé par le compilateur. Cet effet semblerait équilibrer, ou l'emporter sur une solution plus efficace non optimisée.
Optimiser les compilateurs sont un mythe (sauf peut-être dans Haskell). OK, cette déclaration est trop forte, mais elle est extrêmement rare pour un compilateur d'une langue impure i> à faire un meilleur travail de «optimisation» de code pur que pour un humain à l'optimiser à la main avec un code impur. Les compilateurs ne sont tout simplement pas si bon.
@Brian: Étant donné que le matériel sous-jacent est "impur", je pense qu'il est prudent de dire qu'un code impuissant humain optimisant la main suffisamment à la main battra un compilateur de tous les temps. Les compilateurs Haskell sont incroyablement intelligents, mais il y a une raison pour laquelle des choses comme le ST CODE> MONAD existent et que toute la magie profonde de GHC est la plupart des performances similaires à un code impuissant de manière compétente (non optimisée). Sans les vastes garanties statiques fournies par HASKELL, il est fou de s'attendre à ce que d'autres langues soient meilleures (bien que j'ai entendu OCAML puisse parfois obtenir des résultats impressionnants).
Vous êtes un grand fan de déclarations entre parenthèses, n'est-ce pas? :)
C'est coz anglais n'a pas #light :)
@CAMCCANN: OCAML obtient une excellente performance grâce à un itinéraire très différent: en disposant d'un modèle de coût simple et prévisible et d'une conception linguistique qui a tendance à faire rapidement du code. C'est fondamentalement le contraire exact de Haskell.
optimiser les compilateurs sont un mythe i> allez. Vous ne voulez pas par ex. Recherche dans C ++ pour chaque fonction utilisée une fois en code pour écrire en ligne code>, surtout si c'est grand. Mais un compilateur est suffisamment intelligent pour voir l'optimisation du temps de liaison que
appelez code> 'n'
ret code> inutile ici et en ligne. Il peut également révéler qu'une petite boucle exécutée une seule ou deux fois, et vous dérouler cela - mais vous ne voudrez pas le faire explicitement en code. Plus le compilateur peut effectuer des optimisations dépendantes quelles instructions de processeur sont disponibles et même optimiser la taille du cache CPU (la chose que les peuples gentoo aiment) i>.
Je répondrais à cela avec une question: "Écrivez-vous la fonction ou utilisez la fonction?" P>
Il y a 2 aspects aux fonctions, aux utilisateurs et aux développeurs. p>
En tant qu'utilisateur, on ne se soucie pas de la structure interne d'une fonction du tout. Il peut être codé en code d'octet et utiliser des effets secondaires durs en interne à partir de maintenant jusqu'au jour du jugement tant qu'il correspond au contrat de données In-> Données que l'on attend. Une fonction est une boîte noire ou une oracle, sa structure interne est sans importance (en supposant que cela ne fait rien de stupide et externe). P>
En tant que développeur d'une fonction, la structure interne importe beaucoup. Immutabilité, Const la correction et éviter les effets secondaires Tout aident à développer et à conserver une fonction et à élargir la fonction dans le domaine parallèle. P>
Beaucoup de gens développent une fonction qu'ils utilisent ensuite, de sorte que ces deux aspects s'appliquent. P>
Quels sont les avantages d'immutabilité et des structures mutables sont une question différente. P>
Mais c'était exactement la question.
Peut-être changer le titre sur quelque chose de plus direct, comme "sont une fonction pure qui utilise des substances mutables nuisibles internes?"