2011-08-24 6 views
11

Je comprends que la forme binding permet une mise à l'échelle dynamique rebindable dans clojure. Jusqu'à présent, les seules utilisations que je l'ai vu utilisé est pour les E/S telles que print*out* est rebond à tout ce que vous souhaitez écrivain à l'époque.Quels sont de bons exemples d'utilisation de 'binding' dans clojure?

Je voudrais voir des exemples qui tirent vraiment profit de la puissance de binding où d'autres installations ne fonctionnent vraiment pas. Personnellement, je ne l'ai utilisé que dans les cas où passer un objet fourni par l'utilisateur à toutes les fonctions était vraiment fastidieux. Fondamentalement, une situation où j'essaie de créer un contexte que les fonctions d'aide utilise. (Similaire à ce cas When should one use the temporarily-rebind-a-special-var idiom in Clojure?) Pour être plus précis, je comptais sur l'utilisateur pour créer une liaison dynamique à la var *db* pour permettre aux fonctions de base de données de savoir sur quoi opérer. Cela était particulièrement utile lorsque l'utilisateur devait écrire beaucoup d'appels imbriqués dans les fonctions de la base de données. En règle générale, je suis OK si j'ai besoin d'écrire des macros pour rendre les choses plus faciles pour moi, mais demander à l'utilisateur de le faire semble mauvais. Cela étant dit, j'essaie d'éviter de le faire autant que possible.

Quels sont les autres cas d'utilisation de 'binding' que je peux copier et incorporer dans mon code?

+0

à tout le monde, toutes les bonnes réponses jusqu'à présent. Je le laisserai peut-être quelques jours de plus avant de choisir une réponse au cas où quelqu'un d'autre voudrait prendre un coup de couteau. – bmillare

Répondre

8

j'utiliser un pour deux raisons:

  1. l'exécution des tests qui remplacent les constantes ou d'autres valeurs d'autres symboles
  2. en utilisant les ressources « globales » telles que les connexions de base de données ou des canaux de courtier de message

test

Je travaille sur un système distribué avec plusieurs composants qui communiquent en envoyant des messages sur des échanges de messages. Ces échanges ont des noms globaux, que je définis comme tels:

(ns const) 
(def JOB-EXCHANGE "my.job.xchg") 
(def CRUNCH-EXCHANGE "my.crunch.xchg") 
;; ... more constants 

Ces constantes sont utilisées dans un certain nombre d'endroits pour envoyer des messages au bon endroit. Pour tester mon code, une partie de ma suite de tests exécute du code qui utilise les échanges de messages réels. Cependant, je ne veux pas que mes tests interfèrent avec le système actuel.

Pour résoudre ce problème, je conclurai mon code d'essai dans un appel binding qui remplace ces constantes:

;; in my testing code: 
(binding [const/CRUNCH-EXCHANGE (str const/CRUNCH-EXCHANGE (gensym "-TEST-")) 
      const/CRUNCH-TASK-QUEUE (str const/CRUNCH-TASK-QUEUE (gensym "-TEST-"))] 
    ;; tests here 
) 

A l'intérieur de cette fonction binding, je peux appeler un code qui utilise les constantes et il va utiliser la valeurs remplacées

en utilisant les ressources mondiales

Une autre façon j'utiliser un est de « fixer » la valeur d'une ressource globale ou singleton dans une portée particulière.Voici un exemple d'une bibliothèque RabbitMQ je l'ai écrit, où je lie la valeur d'un RabbitMQ Connection au symbole *amqp-connection* afin que mon code peut l'utiliser:

(with-connection (make-connection opts) 
    ;; code that uses a RabbitMQ connection 
) 

La mise en œuvre de with-connection est assez simple:

(def ^{:dynamic true} *amqp-connection* nil) 

(defmacro with-connection 
    "Binds connection to a value you can retrieve 
    with (current-connection) within body." 
    [conn & body] 
    `(binding [*amqp-connection* ~conn] 
    [email protected])) 

Tout code dans ma bibliothèque RabbitMQ peut utiliser la connexion dans *amqp-connection* et supposer qu'il est valide, ouvert Connection. Ou utilisez la fonction (current-connection), qui renvoie une exception descriptive lorsque vous avez oublié d'envelopper vos appels RabbitMQ dans un with-connection:

(defn current-connection 
    "If used within (with-connection conn ...), 
    returns the currently bound connection." 
    [] 
    (if (current-connection?) 
    *amqp-connection* 
    (throw (RuntimeException. 
     "No current connection. Use (with-connection conn ...) to bind a connection.")))) 
+0

L'expression '(if (current-connection?)' Est-elle correcte dans le dernier bloc de code? Peut-elle (ou devrait-elle être) 'if (* amqp-connection *)' ou est-ce que 'current-connection? '(true? * amqp-connection *)' ou '(un? * amqp-connection *)'? –

+1

Kenny, 'current-connection?' n'existe que pour 'cacher' le var '* amqp-connection *' de la vue Son implémentation peut être aussi simple que '(true? * Amqp-connection *)' comme vous l'avez dit.C'est une question de style de code je suppose, il n'y a pas de mal à utiliser le var directement. – Gert

2

En arrière-plan VimClojure vous pourriez avoir plusieurs repls en cours d'exécution dans la même machine virtuelle Java. Cependant, puisque la connexion entre Vim et le backend n'est pas continue, vous obtenez potentiellement un nouveau thread pour chaque commande. Vous ne pouvez donc pas facilement conserver l'état entre les commandes.

Ce que fait VimClojure est le suivant. Il met en place un binding avec tous intéressants Vars comme *warn-on-reflection*, *1, *2, et ainsi de suite. Ensuite, il exécute la commande et stocke ensuite le Vars potentiellement modifié du binding dans une infrastructure bookeeping.

Ainsi, chaque commande dit simplement "j'appartiens à 4711 repl repli" et elle verra l'état de cette repl. Sans affecter l'état de la réponse 0815.

2

les fonctions de liaison peuvent être vraiment utiles dans test code. C'est l'un des grands avantages du stockage des fonctions dans vars (comme le fait Clojure par défaut).

un extrait d'un programme de cryptographie que j'ai écrit.

(defmacro with-fake-prng [ & exprs ] 
    "replaces the prng with one that produces consisten results" 
    `(binding [com.cryptovide.split/get-prng (fn [] (cycle [1 2 3]))] 
    [email protected])) 

Comment testez-vous la fonction d'un générateur de clé? c'est supposé être imprévisible. Vous pourriez filer (if testing ...) partout ou utiliser une sorte de cadre moqueur. ou vous pouvez utiliser une macro qui "se moque dynamiquement" du générateur de nombres aléatoires et mettre cette seulement dans le code de test en laissant votre côté de la production libre de cruft.

(deftest test-key-gen 
    (with-fake-prng 
     ....)) 
Questions connexes