我意识到以下是个坏主意,原因有很多。我还意识到,鉴于我的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
因此,基本上,我希望带有animal的是一个宏。所有出现的"speak"函数调用都映射到我调用块的对象。
特别是,我不想写:
(let-binding [speak (fn [] "woof")] ...)
(let-binding [speak (fn [] "meow")] ...)
相反,我希望with animal使speak函数映射到我调用的对象的某个方法。
在Clojure中有一种干净的方法可以做到这一点吗?
谢谢!
动态绑定的存在是有原因的,它有很多很好的用途,所以不用担心因为试图理解它而被激怒:-)许多旧的Clojure教程中存在一些困惑,这些教程早在需要将^:动态元数据添加到您希望动态重新绑定的变量中。
第一个示例通过重新绑定现有名称来使用动态绑定。这消除了宏引入新符号的需要:
首先制作一些动物,我只是在这个例子中使用地图,许多人会使用其他类型的对象:
(def dog {:sound #(str "wooooof")})
(def cat {:sound #(str "mewwww")})
将我们要重新绑定的函数定义为动态的(允许重新绑定)
(defn :^dynamic speak [] (println "eh?"))
编写一个基本的模板宏来绑定动物中的功能:
(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")] ...)
的愿望。这种类型的maco被称为回指(如果你喜欢这样花哨的名字):重要的部分是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
我希望这两个例子之间的对比有助于回答您关于将行为从对象绑定到范围的问题。第一个示例将maco主体的值与从该主体调用的任何内容绑定。第二个示例介绍了宏主体的ONLY名称。
如果你真的想让动物类型说话习惯,可以使用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类的语法有点不同,有关更多信息,请参阅协议文档。