隐式调用函数的正确版本

时间:2017-02-21 21:14:50

标签: clojure

我有一个应用程序,其中我有一个函数可以在一些不同的上下文中调用,我希望它调用的一些函数在这些不同的上下文中以不同的方式运行。因此,作为一个例子,我可能有的代码

(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)))

这是有效的,并且它不需要进行大量的改变,但我认为它必须是非惯用的。它似乎也可能是一个有奇怪错误的邀请。

这样做的惯用方法是什么?有没有办法做到这一点,同样,需要对代码进行很少的更改?

1 个答案:

答案 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添加,如上所示。