1
votes

Comment clojure lie-t-il les paramètres variables?

Je suis un débutant dans Clojure. Le problème est survenu quand j'ai vérifié une fois le code source de conj :

user=> (def my_conj
   (fn [coll x & xs]
     (println "xs is" xs)
     (if xs
       (recur (clojure.lang.RT/conj coll x) (first xs) (next xs))
       (clojure.lang.RT/conj coll x))))
#'user/my_conj
user=> (my_conj [] 1 (next []))
xs is (nil)
xs is nil
[1 nil]

Le code source de conj montre qu'il utilise recur pour implémenter la fonction. Ce code source semble très simple. Ce qui me trouble, c'est la condition qu'il utilise pour déterminer si la récursivité doit continuer. On dirait qu'il vérifie si les paramètres de la variable sont nil , mais si les paramètres de la variable sont nil , il sera bientôt équivalent à la troisième "arité" de conj ? J'ai ensuite essayé d'évaluer l'expression suivante:

user=> (conj [] 1 (next []))
[1 nil]
user=>

Cela fonctionne normalement et ajoute avec succès nil au vecteur. Je comprends que clojure enveloppe en fait nil dans une liste et la transmet à la fonction, mais je ne comprends pas pourquoi recur peut transmettre un vrai code nil > dans? Et pourquoi clojure reconnaît-il et correspond-il à la bonne "arité"?


    (def conj 
      (fn ^:static conj
        ([] [])
        ([coll] coll)
        ([coll x] (clojure.lang.RT/conj coll x));4
        ([coll x & xs] ;1
         (if xs ;2
           (recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) ;3
           (clojure.lang.RT/conj coll x)))))


0 commentaires

3 Réponses :


0
votes

Je recommanderais de faire une copie de cette fonction, de changer son nom en autre chose et d'ajouter quelques appels à println des valeurs de ce qui vous intéresse, par exemple x, xs, etc., et regardez ce qui est imprimé dans une session Clojure REPL lorsque vous appelez votre fonction avec différents paramètres qui vous intéressent.

Le if xs est vrai si la valeur de xs est n'importe quelle valeur autre que nil ou false . La valeur (nil) est une liste d'un élément, et est considérée comme vraie lorsqu'elle est utilisée comme expression conditionnelle if .


6 commentaires

Merci pour votre réponse, mais j'ai en fait essayé de le faire avant de publier cette question, et le résultat est le même que celui décrit dans la question.


(next []) est évalué à nil , donc votre exemple d'appel (my_conj [] 1 (next []))) est le même que si vous aviez appelé (my_conj [] 1 nil) .


De plus, je ne sais pas si cela prête à confusion, mais clojure.lang.RT / conj est un appel à une méthode Java, pas un appel récursif.


(recur (clojure.lang.RT / conj coll x) (first xs) (next xs)); 3 N'est-ce pas un appel récursif? Et il devrait être simplifié en: ( recur result (first xs) nil) , result est le résultat de l'évaluation de (clojure.lang.RT / conj coll x) , nil < / code> est (xs suivant) car xs ne contient qu'un seul élément: nil .


Encore une chose, dont je ne suis pas sûr à 100%, mais je crois que c'est vrai: lorsque vous appelez conj / my-conj avec exactement 2 arguments, il semble que Clojure puisse appeler la version déclarée avec les paramètres [coll x] ou [coll x & xs] , mais il semble toujours appeler celui déclaré avec [coll x] dans ce cas, lorsqu'il est appelé de l'extérieur . Cependant, l'appel recur ne basculera jamais entre les différentes déclarations - il appellera toujours la définition avec la même arité, quoi qu'il arrive. Je crois que c'est une limitation / propriété de recur dans Clojure.


Je suis d'accord avec cela, mais je ne comprends pas comment Clojure lie un paramètre variable à un nil , car si vous l'appelez de l'extérieur, vous n'obtiendrez qu'un seul (nil) quand même.



0
votes

La partie de la fonction déclarée avec les paramètres [coll x & xs] signifie "lier la valeur du premier paramètre à coll, lier la valeur du deuxième paramètre à x, alors s'il y a plus de paramètres, liez xs avec la valeur nil, sinon liez xs avec la valeur qui est la liste des paramètres restants ".

Vous pouvez voir cela avec cette fonction plus simple qui n'utilise pas du tout recur:

user=> (defn my-fn [a & xs]
         (println "a=" a " xs=" xs))

user=> (my-fn 1)
a= 1  xs= nil

user=> (my-fn 1 2)
a= 1  xs= (2)


1 commentaires

Mais comment évaluer cette forme: (recur (clojure.lang.RT / conj coll x) (first xs) (next xs)); 3 ? Contrairement à votre exemple, recur déclare vraiment des paramètres supplémentaires! Si vous utilisez cet exemple pour l'expliquer, il peut finir par être évalué comme (recur (clojure.lang.RT / conj coll x) (first xs)); 3 . Comme le dernier paramètre est "nil", il ne sera pas transmis. Je pense qu'il est logique d'expliquer cela, peut-être que recur a fait quelque chose, mais je ne trouve pas d'autres informations pour le prouver.



1
votes

OK, je m'excuse de ne pas avoir réalisé plus tôt que vous avez rencontré un aspect du comportement de Clojure que je n'avais jamais vu auparavant. Aujourd'hui, j'ai appris quelque chose de nouveau sur Clojure, et compte tenu de 10 ans, cela m'a surpris.

Ceci est mentionné dans quelques phrases de la documentation officielle de Clojure pour recur, ici: https: / /clojure.org/reference/special_forms#recur

Voici une page d'exemples écrits par la communauté et de documentation (donc pas "officielle") qui décrit ce comportement de récurrence avec des fonctions qui ont des arguments variadiques: https://clojuredocs.org/clojure.core/recur#example-55ff3cd4e4b08e404b6c1c7f


0 commentaires