我有一个应用程序,其中我有一个函数可以在一些不同的上下文中调用,我希望它调用的一些函数在这些不同的上下文中以不同的方式运行。因此,作为一个例子,我可能有的代码:
(defn foo [a context]
(-> a
inc
(#(bar % context))))
(defn bar [a context]
(cond (= context 1) (* a 2)
(= context 2) (/ a 2)))
虽然有很多不同的功能,比如bar,它们被埋没在其他功能中,而这些功能本身并不关心" context"。
因为它们被隐藏在一堆其他函数中(并且因为我为一个上下文编写了这个代码,现在正在添加其他函数),修改所有这些函数以将标记传递给所有相关的函数。 #39;酒吧很麻烦。我也不喜欢它作为解决方案。理想情况下,我想在每个上下文中隐式使用正确版本的bar函数。
协议可能会解决问题。我认为使用协议重写我的功能将是一个巨大的麻烦,因为(在我的实际功能中)一些' bar'函数都使用上下文并将其传递给使用它的其他函数。所以我想我必须复制一些代码(或者有不同的协议传递标志)。
我提出的解决方案是为foo创建一个名称空间,然后为每个上下文创建一个单独的名称空间。然后,在每个上下文的命名空间中,我定义一个单独的命名空间。我改变了foo,以便它调用驻留在调用命名空间中的bar函数的版本。那就是:
(ns main)
(defn foo [a]
(-> inc
('bar (ns-map *ns*))))
(ns context-1
(use main))
(defn bar [a]
(* a 2))
(ns context-2
(use main))
(defn bar [a]
(/ a 2))
然后当我从context-1命名空间调用foo时,它按预期工作。对于context-2名称空间也是如此。
这基本上有效,除了因为我想从不同的命名空间调用上下文1 foo和上下文2 foo,我需要为我进入的每个命名空间编写一个包装器foo函数调用的命名空间然后切换回我开始的命名空间。所以,在上下文中1 ns,我写了类似的东西:
(defn context-1-foo [a]
(let [base-ns *ns*]
(in-ns 'context-1)
(let [result (foo a)]
(in-ns (ns-name base-ns))
result)))
这是有效的,并且它不需要进行大量的改变,但我认为它必须是非惯用的。它似乎也可能是一个有奇怪错误的邀请。
这样做的惯用方法是什么?有没有办法做到这一点,同样,需要对代码进行很少的更改?
答案 0 :(得分:1)
<强>更新强>
道歉,在重新阅读问题后,我发现这可能无法解决您对上下文隐式的要求。你可能会更好地研究Dynamic Binding,你可以创建一个宏来工作:
(with-context 1
(bar 4))
;; => 8
(with-context 2
(bar 4))
;; => 2
原始答案
处理这种情况的惯用方法是使用Clojure的multimethods。如果要调用的方法取决于上下文中的内容,那么multimethods将允许您调度与特定上下文匹配的方法,并且它是面向未来的,因为您可以通过简单地指定它将匹配的上下文来添加更多方法。 / p>
对于你的例子:
; you could define the context object like:
{:method :bar-1}
; or
{:method :bar-2}
;; Create multimethod that accepts the 2 params
;; and dispatches on :method in context
(defmulti bar (fn [a context] (:method context)))
;; Method that dispatches when context is {:method :bar-1}
(defmethod bar :bar-1 [a context]
(* a 2))
;; Method that dispatches when context is {:method :bar-2}
(defmethod bar :bar-2 [a context]
(/ a 2))
;; Method that dispatches when context is {:method :bar-3}
(defmethod bar :bar-3 [a context]
; Some third implementation
)
然后你只需用正确的上下文对象
来调用bar
(bar 4 {:method :bar-1})
;; => 8
(bar 4 {:method :bar-2})
;; => 2
(bar 4 {:method :bar-3})
;; => nil
任何未来的bar实现都可以使用简单的defmethod
添加,如上所示。