Cependant, lequel est le plus idiomatique clojure? Cela fait-il une grande différence dans un sens ou dans l'autre? D'après mes tests de performances (limités), il semble que reducec'est un peu plus rapide.
reduceet ne applysont bien sûr équivalentes (en termes de résultat final renvoyé) que pour les fonctions associatives qui ont besoin de voir tous leurs arguments dans le cas de l'arité variable. Quand ils sont équivalents en termes de résultats, je dirais que applyc'est toujours parfaitement idiomatique, alors que cela reduceest équivalent - et pourrait raser une fraction d'un clin d'œil - dans de nombreux cas courants. Ce qui suit est ma raison de croire cela.
+est lui-même implémenté en termes de reducepour le cas d'arité variable (plus de 2 arguments). En effet, cela semble être une manière "par défaut" extrêmement sensible d'aller pour toute fonction associative à arité variable: reducea le potentiel d'effectuer des optimisations pour accélérer les choses - peut-être par quelque chose comme internal-reduce, une nouveauté 1.2 récemment désactivée dans master, mais j'espère être réintroduit dans le futur - qu'il serait ridicule de reproduire dans toutes les fonctions qui pourraient en bénéficier dans le cas vararg. Dans ces cas courants, applyajoutera simplement un peu de frais généraux. (Notez qu'il n'y a pas de quoi s'inquiéter vraiment.)
D'un autre côté, une fonction complexe peut tirer parti de certaines opportunités d'optimisation qui ne sont pas assez générales pour être intégrées reduce; alors applyvous permettrait de profiter de ceux-ci tout reduceen vous ralentissant. Un bon exemple de ce dernier scénario se produisant dans la pratique est fourni par str: il utilise un en StringBuilderinterne et bénéficiera considérablement de l'utilisation de applyplutôt que reduce.
Donc, je dirais utiliser en applycas de doute; et si vous savez que cela ne vous achète rien reduce(et que cela ne changera probablement pas très bientôt), n'hésitez pas à utiliser reducepour raser cette minuscule surcharge inutile si vous en avez envie.
Très bonne réponse. Sur une note latérale, pourquoi ne pas inclure une sumfonction intégrée comme dans haskell? Cela semble être une opération assez courante.
dbyrne
17
Merci, heureux de l'entendre! Re sum:, je dirais que Clojure a cette fonction, elle est appelée +et vous pouvez l'utiliser avec apply. :-) Sérieusement, je pense qu'en Lisp, en général, si une fonction variadique est fournie, elle n'est généralement pas accompagnée d'un wrapper fonctionnant sur des collections - c'est ce que vous utilisez applypour (ou reduce, si vous savez que cela a plus de sens).
Michał Marczyk
6
C'est drôle, mon conseil est le contraire: en reducecas de doute, applyquand on est sûr qu'il y a une optimisation. reduceLe contrat est plus précis et donc plus sujet à une optimisation générale. applyest plus vague et ne peut donc être optimisée qu'au cas par cas. stret concatsont les deux exceptions courantes.
cgrand
1
@cgrand Une reformulation de ma justification pourrait être à peu près que pour les fonctions où reduceet applysont équivalentes en termes de résultats, je m'attendrais à ce que l'auteur de la fonction en question sache comment optimiser au mieux leur surcharge variadique et l'implémenter simplement en termes de reducesi c'est en effet ce qui a le plus de sens (l'option pour le faire est certainement toujours disponible et constitue un défaut éminemment raisonnable). Je vois cependant d'où vous venez, reducec'est définitivement au cœur de l'histoire de la performance de Clojure (et de plus en plus), très hautement optimisée et très clairement spécifiée.
Michał Marczyk
51
Pour les débutants qui regardent cette réponse,
faites attention, ce ne sont pas les mêmes:
Les opinions varient - Dans le plus grand monde Lisp, reduceest définitivement considéré comme plus idiomatique. Premièrement, il y a les problèmes variadiques déjà discutés. De plus, certains compilateurs Common Lisp échoueront en fait lorsqu'ils applysont appliqués à de très longues listes en raison de la façon dont ils gèrent les listes d'arguments.
Parmi les Clojurists de mon entourage, cependant, l'utilisation applydans ce cas semble plus courante. Je trouve plus facile de grok et je préfère ça aussi.
Cela ne fait aucune différence dans ce cas, car + est un cas spécial qui peut s'appliquer à n'importe quel nombre d'arguments. Réduire est un moyen d'appliquer une fonction qui attend un nombre fixe d'arguments (2) à une liste d'arguments arbitrairement longue.
Je préfère généralement réduire lorsque j'agis sur n'importe quel type de collection - cela fonctionne bien et c'est une fonction assez utile en général.
La principale raison pour laquelle j'utiliserais apply est si les paramètres signifient des choses différentes dans différentes positions, ou si vous avez quelques paramètres initiaux mais que vous souhaitez obtenir le reste d'une collection, par exemple
Lorsque vous utilisez une fonction simple comme +, peu importe celle que vous utilisez.
En général, l'idée est qu'il reduces'agit d'une opération cumulative. Vous présentez la valeur d'accumulation actuelle et une nouvelle valeur à votre fonction d'accumulation. Le résultat de la fonction est la valeur cumulée pour l'itération suivante. Ainsi, vos itérations ressemblent à:
cum-val[i+1] = F( cum-val[i], input-val[i]); please forgive the java-like syntax!
Pour apply, l'idée est que vous essayez d'appeler une fonction en attendant un certain nombre d'arguments scalaires, mais ils sont actuellement dans une collection et doivent être retirés. Donc, au lieu de dire:
Un peu tard sur le sujet mais j'ai fait une expérience simple après avoir lu cet exemple. Voici le résultat de ma réplique, je ne peux tout simplement rien déduire de la réponse, mais il semble qu'il y ait une sorte de coup de pied de mise en cache entre réduire et appliquer.
En regardant le code source de clojure réduire sa récursivité assez propre avec internal-Reduce, rien n'a été trouvé sur l'implémentation de apply. L'implémentation de Clojure de + pour apply en interne appelle la réduction, qui est mise en cache par repl, ce qui semble expliquer le 4ème appel. Quelqu'un peut-il clarifier ce qui se passe réellement ici?
Je sais que je préférerai réduire quand je peux :)
rohit
2
Vous ne devriez pas mettre l' rangeappel dans le timeformulaire. Mettez-le à l'extérieur pour supprimer l'interférence de la construction de la séquence. Dans mon cas, reducesurpasse constamment apply.
Davyzhu
3
La beauté de la fonction apply est donnée (+ dans ce cas) peut être appliquée à une liste d'arguments formée par des arguments intermédiaires pré-en attente avec une collection de fin. Réduire est une abstraction pour traiter les éléments de collection en appliquant la fonction pour chacun et ne fonctionne pas avec des arguments variables.
(apply + 1 2 3 [3 4])
=> 13
(reduce + 1 2 3 [3 4])
ArityException Wrong number of args (5) passed to: core/reduce clojure.lang.AFn.throwArity (AFn.java:429)
sum
fonction intégrée comme dans haskell? Cela semble être une opération assez courante.sum
:, je dirais que Clojure a cette fonction, elle est appelée+
et vous pouvez l'utiliser avecapply
. :-) Sérieusement, je pense qu'en Lisp, en général, si une fonction variadique est fournie, elle n'est généralement pas accompagnée d'un wrapper fonctionnant sur des collections - c'est ce que vous utilisezapply
pour (oureduce
, si vous savez que cela a plus de sens).reduce
cas de doute,apply
quand on est sûr qu'il y a une optimisation.reduce
Le contrat est plus précis et donc plus sujet à une optimisation générale.apply
est plus vague et ne peut donc être optimisée qu'au cas par cas.str
etconcat
sont les deux exceptions courantes.reduce
etapply
sont équivalentes en termes de résultats, je m'attendrais à ce que l'auteur de la fonction en question sache comment optimiser au mieux leur surcharge variadique et l'implémenter simplement en termes dereduce
si c'est en effet ce qui a le plus de sens (l'option pour le faire est certainement toujours disponible et constitue un défaut éminemment raisonnable). Je vois cependant d'où vous venez,reduce
c'est définitivement au cœur de l'histoire de la performance de Clojure (et de plus en plus), très hautement optimisée et très clairement spécifiée.Pour les débutants qui regardent cette réponse,
faites attention, ce ne sont pas les mêmes:
la source
Les opinions varient - Dans le plus grand monde Lisp,
reduce
est définitivement considéré comme plus idiomatique. Premièrement, il y a les problèmes variadiques déjà discutés. De plus, certains compilateurs Common Lisp échoueront en fait lorsqu'ilsapply
sont appliqués à de très longues listes en raison de la façon dont ils gèrent les listes d'arguments.Parmi les Clojurists de mon entourage, cependant, l'utilisation
apply
dans ce cas semble plus courante. Je trouve plus facile de grok et je préfère ça aussi.la source
Cela ne fait aucune différence dans ce cas, car + est un cas spécial qui peut s'appliquer à n'importe quel nombre d'arguments. Réduire est un moyen d'appliquer une fonction qui attend un nombre fixe d'arguments (2) à une liste d'arguments arbitrairement longue.
la source
Je préfère généralement réduire lorsque j'agis sur n'importe quel type de collection - cela fonctionne bien et c'est une fonction assez utile en général.
La principale raison pour laquelle j'utiliserais apply est si les paramètres signifient des choses différentes dans différentes positions, ou si vous avez quelques paramètres initiaux mais que vous souhaitez obtenir le reste d'une collection, par exemple
la source
Dans ce cas précis je préfère
reduce
car c'est plus lisible : quand je lisJe sais immédiatement que vous transformez une séquence en valeur.
Avec
apply
je dois considérer quelle fonction est appliquée: "ah, c'est la+
fonction, donc je reçois ... un seul nombre". Un peu moins simple.la source
Lorsque vous utilisez une fonction simple comme +, peu importe celle que vous utilisez.
En général, l'idée est qu'il
reduce
s'agit d'une opération cumulative. Vous présentez la valeur d'accumulation actuelle et une nouvelle valeur à votre fonction d'accumulation. Le résultat de la fonction est la valeur cumulée pour l'itération suivante. Ainsi, vos itérations ressemblent à:Pour apply, l'idée est que vous essayez d'appeler une fonction en attendant un certain nombre d'arguments scalaires, mais ils sont actuellement dans une collection et doivent être retirés. Donc, au lieu de dire:
On peut dire:
et il est converti pour être équivalent à:
Donc, utiliser "appliquer" revient à "supprimer les parenthèses" autour de la séquence.
la source
Un peu tard sur le sujet mais j'ai fait une expérience simple après avoir lu cet exemple. Voici le résultat de ma réplique, je ne peux tout simplement rien déduire de la réponse, mais il semble qu'il y ait une sorte de coup de pied de mise en cache entre réduire et appliquer.
En regardant le code source de clojure réduire sa récursivité assez propre avec internal-Reduce, rien n'a été trouvé sur l'implémentation de apply. L'implémentation de Clojure de + pour apply en interne appelle la réduction, qui est mise en cache par repl, ce qui semble expliquer le 4ème appel. Quelqu'un peut-il clarifier ce qui se passe réellement ici?
la source
range
appel dans letime
formulaire. Mettez-le à l'extérieur pour supprimer l'interférence de la construction de la séquence. Dans mon cas,reduce
surpasse constammentapply
.La beauté de la fonction apply est donnée (+ dans ce cas) peut être appliquée à une liste d'arguments formée par des arguments intermédiaires pré-en attente avec une collection de fin. Réduire est une abstraction pour traiter les éléments de collection en appliquant la fonction pour chacun et ne fonctionne pas avec des arguments variables.
la source