32 votes

Expliquer les symboles Clojure

J'ai un symbole "a" lié à une fonction :

(defn a []
    (println "Hello, World"))

user=> a
#<user$a__292 user$a__292@97eded>
user=> (a)    
Hello, World
nil

Ensuite, j'utilise syntax-quote, qui "résout le symbole dans le contexte actuel, en produisant un symbole entièrement qualifié", d'après Documentation Clojure . Mais pourquoi ne puis-je pas l'utiliser de la même manière qu'un symbole non qualifié ?

user=> `a
user/a
user=> (`a)
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)

Deuxième question : si j'ai un symbole dans une liste, pourquoi ne puis-je pas l'évaluer de la même manière que si j'évaluais le symbole directement ?

user=> (def l '(a 1 2))
#'user/l
user=> 'l
l
user=> (first l)
a
user=> ((first l))
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)

J'ai le sentiment qu'il y a une faille fatale quelque part dans la compréhension fondamentale du fonctionnement des symboles. Quel est le problème avec le code ci-dessus ?

36voto

Brian Carper Points 40078

REPL = lire eval imprimer boucle. Avancez dans le processus read-eval.

READ : Clojure voit la chaîne de caractères "(`a)" l'analyse et aboutit à une structure de données. Au moment de la lecture, les macros du lecteur sont développées et il ne se passe pas grand-chose d'autre. Dans ce cas, le lecteur développe la rétro-citation et obtient ceci :

user> (read-string "(`a)")
((quote user/a))

EVAL : Clojure essaie d'évaluer cet objet. Les règles d'évaluation varient en fonction du type d'objet que vous regardez.

  • Certains objets sont évalués comme eux-mêmes (nombres, chaînes de caractères, mots-clés, etc.).
  • Un symbole est évalué en le résolvant dans un espace de nom pour obtenir une valeur (généralement).
  • Une liste est évaluée par macro-expansion de la liste jusqu'à ce qu'il n'y ait plus de macros, puis par évaluation récursive du premier élément de la liste pour obtenir une valeur de valeur résultante puis en utilisant le valeur du premier élément de la liste pour décider de la marche à suivre. Si la première valeur est une forme spéciale, des choses spéciales se produisent. Sinon, la première valeur est traitée comme une fonction et appelée avec les valeurs du reste de la liste (obtenues en évaluant récursivement tous les éléments de la liste) comme paramètres.
  • etc.

Se référer à clojure.lang.Compiler/analyzeSeq dans le code source Clojure pour voir les règles d'évaluation des listes, ou bien clojure.lang.Compiler/analyzeSymbol pour les symboles. Il y a beaucoup d'autres règles d'évaluation.

Exemple

Supposons que vous fassiez ça :

user> (user/a)

Le REPL finit par faire cela en interne :

user> (eval '(user/a))

Clojure voit que vous évaluez une liste, il évalue donc tous les éléments de la liste. Le premier (et seul) élément :

user> (eval 'user/a)
#<user$a__1811 user$a__1811@82c23d>

a n'est pas une forme spéciale et cette liste n'a pas besoin d'être macro-expansée, donc le symbole a est recherché dans l'espace de nom user et le valeur résultante voici un fn . Donc, ceci fn s'appelle.

Votre code

Mais à la place, vous avez ceci :

user> (eval '((quote user/a)))

Clojure évalue le premier élément de la liste, qui est elle-même une liste.

user> (eval '(quote user/a))
user/a

Il a évalué le premier élément de cette sous-liste, quote qui est une forme spéciale, donc des règles spéciales s'appliquent et elle retourne son argument (le symbole a ) non évalués.

Le symbole a est le valeur dans ce cas comme le fn était la valeur ci-dessus. Clojure traite donc le symbole lui-même comme une fonction et l'appelle. Dans Clojure, tout ce qui implémente la fonction Ifn est appelable comme une interface fn . Il se trouve que clojure.lang.Symbol met en œuvre Ifn . Un symbole appelé en tant que fonction n'attend qu'un seul paramètre, une collection, et il se cherche lui-même dans cette collection. Il est destiné à être utilisé comme ceci :

user> ('a {'a :foo})
:foo

C'est ce qu'il essaie de faire ici. Mais vous ne passez aucun paramètre, donc vous obtenez l'erreur "Wrong number of args passed to : Symbol" (il s'attend à une collection).

Pour que votre code fonctionne, il vous faudrait deux niveaux de eval . Cela fonctionne, j'espère que vous pouvez voir pourquoi :

user> (eval '((eval (quote user/a))))
Hello, world
user> ((eval (first l)))
Hello, world

Notez que dans le code réel, l'utilisation de eval directement est généralement une très mauvaise idée. Les macros sont de loin une meilleure idée. Je ne l'utilise ici que pour la démonstration.

Regardez Compiler.java dans les sources de Clojure pour voir comment tout cela se passe. Ce n'est pas trop difficile à suivre.

15voto

Chris Vest Points 5622

Utiliser un symbole comme fonction n'est pas la même chose que l'évaluer. Les symboles en tant que fonctions fonctionnent de la même manière que les mots-clés en tant que fonctions. Comme ceci :

user=> (declare a)
#'user/a
user=> (def a-map {'a "value"})
#'user/a-map
user=> ('a a-map)
"value"
user=>

Ce n'est pas la façon dont on utilise normalement un symbole. Ils sont plus souvent utilisés pour rechercher des variables dans un espace de noms et pour générer du code dans une macro.

Pour décomposer les couches d'indirection, définissons "x" comme 1 et voyons ce qui se passe :

user=> (def x 1)
#'user/x

Utilisation de def nous avons créé un "var". Le nom de la var est le symbole user/x. Le site def Le formulaire spécial renvoie la var elle-même à la repl, et voici ce que nous pouvons voir imprimé. Essayons de mettre la main sur cette var :

user=> #'x
#'user/x

El #' La syntaxe est une macro de lecteur qui dit "donne-moi la variable désignée par le symbole suivant". Et dans notre cas, ce symbole est "x". On a récupéré la même var qu'avant. Les var sont des pointeurs vers des valeurs, et peuvent être déréférencés :

user=> (deref #'x)
1

Mais la variable doit être trouvée avant de pouvoir être déréférencée. C'est là que l'appelabilité des symboles entre en jeu. Un espace de noms est comme une carte, où les symboles sont des clés et les var des valeurs, et lorsque nous nommons un symbole, nous recherchons implicitement son var dans notre espace de noms. Comme ceci :

user=> ('x (.getMappings *ns*))
#'user/x

Bien que, en réalité, c'est probablement plus comme ça :

user=> (.findInternedVar *ns* 'x)
#'user/x

Et maintenant, nous avons bouclé la boucle du voyage du symbole non cité :

user=> (deref (.findInternedVar *ns* 'x))
1
user=> x
1

Les deux ne sont cependant pas entièrement égaux. Parce que l'évaluateur fait cela pour tous les symboles, y compris les symboles deref et *ns*.

L'intérêt de la citation est qu'elle permet de contourner tout ce mécanisme et de récupérer le symbole simple. Comme le #' récupère des variables simples, les macros de lecture ` et ' récupèrent des symboles simples, avec ou sans qualification d'espace de nom respectivement :

user=> 'x
x
user=> `x
user/x

7voto

pmf Points 3796

user=> (def l '(a 1 2)) user=> ((first l))

Transformez ça en :

user=> (def l `(~a 1 2))

Ici, le ~ résout le symbole a en son var correspondant, et le backtick permet de supprimer les guillemets.

En général, vous devez comprendre la différence entre les variables (qui sont liées à quelque chose) et les symboles (qui ne sont jamais liés à quoi que ce soit).

Je vais essayer de l'expliquer (en espérant que mon exaplanation ne vous embrouille pas davantage) :

user=> (def v "content")
#'user/content

-> définit une variable dans l'espace de noms actuel sous le symbole 'v (entièrement qualifié 'user/v, en supposant qu'il s'agit de l'espace de noms actuel), et la lie (la variable, pas le symbole) à l'objet "content".

user=> v
"content"

-> résout v à la var, et obtient la valeur liée

user=> #'v
#'user/v

-> se résout à la var elle-même

user=> 'v
v

-> ne résout rien, juste un simple symbole (malheureusement, le REPL ne l'indique pas, imprimant 'v comme v)

user=> `v
user/v

-> comme vous l'avez déjà cité, se résout au symbole dans le contexte actuel (espace de noms), mais le résultat est toujours un symbole (pleinement qualifié), et non le var user/v

user=> '(v)
(v)

-> citation pure et simple, ne résout rien

user=> `(v)
(user/v)

-> syntax-quote, identique à la citation, mais résout les symboles en symboles qualifiés dans l'espace de noms.

user=> `(~v)
("content")

-> résoudre le symbole en sa var (qui est déréférencée implicitement), en produisant son objet lié.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X