【问题标题】:Implicitly calling the correct version of a function隐式调用正确版本的函数
【发布时间】:2017-02-21 21:14:49
【问题描述】:

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

(defn foo [a context]
  (-> a
      inc
      (#(bar % context))))

(defn bar [a context]
  (cond (= context 1) (* a 2)
        (= context 2) (/ a 2)))

尽管有一堆不同的函数,比如 bar,它们隐藏在其他函数中,它们本身并不关心“上下文”。

因为它们隐藏在一堆其他函数中(而且因为我为一个上下文编写了这段代码,现在正在添加其他函数),所以修改所有这些函数以将标志传递给所有相关的 'bar's麻烦。我也不喜欢它作为解决方案。理想情况下,我想在每个上下文中隐式使用正确版本的 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 函数调用的命名空间然后切换回我开始时使用的命名空间。因此,在 context-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)))

这行得通,并且不需要进行大量更改,但我认为它必须是非惯用的。似乎也可能是邀请出现奇怪的错误。

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

【问题讨论】:

    标签: clojure


    【解决方案1】:

    更新:

    抱歉,在重新阅读问题后,我发现这可能无法解决您对上下文隐式的要求。查看Dynamic Binding 可能会更幸运,您可以创建一个宏以如下方式工作:

    (with-context 1
      (bar 4))
    ;; => 8
    
    (with-context 2
      (bar 4))
    ;; => 2
    

    原答案

    处理这种情况的惯用方法是使用 Clojure 的 multimethods。如果要调用的方法取决于上下文中的内容,那么多方法将允许您调度与特定上下文匹配的方法,并且它是面向未来的,因为您可以通过简单地指定它将匹配的上下文来添加更多方法。

    你的例子:

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

    【讨论】:

    • 谢谢你的链接,我不知道多方法。就像你说的,它们不是隐式的,所以我也会看看动态绑定。由于上下文的使用深度,我宁愿隐式执行它,但如果隐式执行它太尴尬,那么我可能会使用多方法惯用地重写它,因为我知道它们。
    • 假设我有多个 bar 函数,bar-1、bar-2、bar-3、bar-4 等,我希望它们中的每一个在上下文 1 和 2 中都有不同的行为. 使用动态绑定,我是否写了一个带有上下文的宏,这样当我调用 (with-context 1 (big-fn-containing-bars input)) 时,它会重新绑定 bar-1、bar-2、bar-3 等。每个功能的上下文 1 版本?我是否正确理解了这里的想法?
    • 我阅读了更多内容并自己测试了一些东西。谢谢,我认为动态绑定正是我所需要的。这真的很酷。
    • 没有问题。很高兴我能帮上忙。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-08
    • 2013-04-27
    • 2015-02-01
    • 1970-01-01
    相关资源
    最近更新 更多