2017-02-21 22 views
2

У меня есть приложение, в котором у меня есть одна функция, которая может быть вызвана в некоторых разных контекстах, и я хочу, чтобы некоторые из функций, которые она вызывает, работали по-разному в этих разных контекстах. Так, в качестве примера, я мог бы иметь код , который функционирует как:Неявно вызывать правильную версию функции

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

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

Albeit с кучей различных функций, таких как бар, которые захоронены внутри других функций, которые сами не заботятся о «контексте».

Потому что они зарыты в кучу других функций (и потому, что я написал этот код для одного контекста, а теперь добавляю другие), изменяя все эти функции, чтобы передать флаг всем соответствующим «барам» это хлопот. Мне тоже не нравится это как решение. В идеале я хотел бы косвенно использовать правильную версию функции бара в каждом контексте.

Протоколы могут решить проблему. Я думаю, что было бы огромной проблемой переписать мою функцию с использованием протоколов, хотя, поскольку (в моих действительных функциях) некоторые из функций «bar» используют контекст и передают его другим функциям, которые его используют. Поэтому я думаю, что мне придется дублировать некоторые из кода (или иметь разные протоколы, пропускающие флаги).

Решение, с которым я столкнулся, заключалось в создании пространства имен для foo, а затем отдельного пространства имен для каждого контекста. В пространстве имен каждого контекста я определяю отдельное пространство имен. И я меняю foo так, чтобы он вызывал версию функции бара, находящуюся в вызывающем пространстве имен. То есть:

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

Тогда, когда я вызываю foo из пространства имен context-1, он работает по назначению. То же самое для пространства имен context-2.

Это в основном работает, за исключением того, что, так как я хочу, чтобы затем вызвать контекстные 1 Foo и Foo контекст 2 из различных имен, мне нужно написать обертку для каждого пространства имен, в котором я иду в это пространство имен для вызов функции Foo затем переключиться обратно в пространстве имен я начал в Таким образом, в контекстно-1 нс, я что-то вроде пишу:.

(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

Update:

Извинения, после перечитывая вопрос я вижу, что это не может решить вы потребность в контексте быть неявное. Вы бы, вероятно, лучше удач глядя в 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 

Любое будущее осуществление бара могут быть добавлены с помощью простого defmethod, как показано выше.

+0

Спасибо за ссылки, я не знал о мультиметодах. Как вы сказали, они не подразумеваются, поэтому я также рассмотрю динамическое связывание. Я бы предпочел сделать это неявно из-за того, насколько глубоко используется контекст, но если делать это неявно, это слишком сложно, тогда я, вероятно, перечислим его идиоматически с помощью нескольких методов, которые я знаю о них. – Nick

+0

Предположим, что у меня есть несколько функций бара, bar-1, bar-2, bar-3, bar-4 и т. Д., И я хочу, чтобы каждый из них имел различное поведение в контексте 1 и 2. С динамическим связыванием, напишите макрос с-контекстом, чтобы при вызове (с использованием контекста 1 (вход в формате большого числа fn-содержащих-баров)) он перепроверяет строки-1, bar-2, bar-3 и т. д. в контексте 1 версии каждого этих функций? Я правильно понимаю эту идею? – Nick

+0

Я читал немного больше и проверял некоторые вещи самостоятельно. Спасибо, я думаю, что динамическое связывание - именно то, что мне нужно. Это действительно здорово. – Nick