Clojure动态绑定

8
我知道以下做法有很多问题。同时,鉴于我的stackoverflow声望只有23分,人们可能会认为我是一个正在学习编程的新手。但是,请容忍我,并且关注“我们如何做到这一点”,而不是“为什么要这样做/你不应该这样做”的方面。
我想要的是:
(def dog (Dog. ...))
(def cat (Cat. ...))

(with-animal dog
  (println (str "Dog: " (speak) "\n")))
(with-animal cat
  (println (str "Cat: " (speak) "\n")))

输出:

Dog: woof
Cat: meow

所以基本上,我希望with-animal成为一个宏,使得所有“speak”函数调用的出现都映射到我正在调用该块的对象上。
特别地,我不想写:
(let-binding [speak (fn [] "woof")] ...)
(let-binding [speak (fn [] "meow")] ...)

我希望with-animal函数能够映射到我所调用的对象的某个方法,与此相关的是it技术。

在Clojure中是否有一种简洁的方式来实现这个功能呢?

谢谢!


我喜欢免责声明 :) - szymanowski
2个回答

20

动态绑定存在的原因是有很多出色的用途,所以不用担心因为寻求理解而被批评 :-)很多早期的Clojure教程中存在一些混淆,这些教程在需要为你预期动态重新绑定的变量添加^:dynamic元数据之前编写。

第一个例子通过重新绑定现有名称来使用动态绑定。这消除了宏引入新符号的必要性:


首先创建一些动物,在此示例中我只使用映射,许多人将使用其他类型的对象:

(def dog {:sound #(str "wooooof")})
(def cat {:sound #(str "mewwww")})

定义我们将重新绑定为动态的函数(允许重新绑定)。

(defn :^dynamic speak [] (println "eh?"))
编写一个基本的模板宏将speak绑定到animal函数中。
(defmacro with-animal [animal & body] 
    `(binding [speak (:sound ~animal)] 
       ~@body))

并测试它:

(with-animal dog  
  (println (str "Dog: " (speak) "\n")))
Dog: wooooof                                                   


现在是“高级版”,只需使用 let 引入一个符号 speak 到作用域中,而不需要动态绑定。这并不意味着绑定有什么不好之处,只是更符合你不想写 (let-binding [speak (fn [] "meow")] ...) 的愿望。如果您对此类花哨的名称感兴趣,这种类型的宏称为指示性的(anaphoric):

重要的部分是在 speak 符号前面加上 ~',这明确地将未限定的符号引入作用域:

user> (defmacro with-animal [animal & body]
    `(let [~'speak (:sound ~animal)] 
        ~@body))
#'user/with-animal

user> (with-animal dog 
        (println (str "Dog: " (speak) "\n")))
Dog: wooooof 

nil


我希望这两个例子的对比可以回答你关于将对象绑定到作用域中的绑定行为的问题。第一个示例将宏主体的值绑定到任何从该主体调用的内容上。第二个示例仅为宏主体引入名称。


我喜欢这个解决方案。我可以想象自己拥有一个定义所有与动物相关函数的地方,然后为每个动物在那个文件中定义它。 - user1647794
1
“speak” 在这两个例子中都不需要成为一个函数,对吧?它只需要是 (def ^:dynamic speak "eh?") 就可以了。因为你无论如何都只是将它连接到一个字符串中。 - Ben

0

如果你真的想让动物类型以惯用语言说话,使用Clojure协议:

(defprotocol ISpeak
  (speak [animal] "make the type say it's thing"))

(deftype Dog []
  ISpeak
  (speak [this] "Woof!"))

(deftype Cat []
  ISpeak
  (speak [_] "Meow!!")) ;;you can "drop" the item if not used using _

(def a-dog (Dog.))
(speak a-dog)
;;=>"Woof!"

(def a-cat (Cat.))
(speak a-cat)
;;=>"Meow!!"

请注意,您可以使用speak方法扩展任何类型(类)。
(extend java.util.Random
  ISpeak
  {:speak (fn [_] "I'm throwing dices at you!")})

(speak (java.util.Random.))
;;=>"I'm throwing dices at you!"

Java类的语法有些不同,请参阅协议文档以获取更多信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接