How do you explicitly specify a namespace when using “apply” on a function in Clojure?

[亡魂溺海] 提交于 2020-01-04 14:03:11

问题


Here "graph" is higher-order function that returns a function with config set in its scope:

(ns bulbs.neo4jserver.graph)

(defn out1
  "Test func that simply returns out1."
  [config]
  "out1")

(defn graph
  [config]
  (fn [func & args]
    (apply func config args)))

You create an instance of graph, which can then be used to call other functions and automatically pass in the config arg:

(def g (graph {:root-uri "http://localhost"}))

(g out1)
;; => "out1"

This works; however, if you require/import graph into another namespace, then you have to prefix each function call with the graph namespace:

(ns bulbs.neo4jserver.junk
  (:require [bulbs.neo4jserver.graph :as graph]))

(def g (graph/graph {:root-uri "http://localhost"}))

;; would rather do (g out1)
(g graph/out1)

Instead, I want to explicitly specify the namespace in the apply function so that users don't have to:

(defn graph
  [config]
  (fn [func & args]
    ;; somehow specify the graph namespace here
    (apply func config args)))

What's the best way to do this?


回答1:


You can pass symbol instead of function and resolve it in graph function:

(defn graph
  [config]
  (fn [func & args]
    (apply (ns-resolve 'bulbs.neo4jserver.graph func) config args)))

And call it:

(ns bulbs.neo4jserver.junk
  (:require [bulbs.neo4jserver.graph :as graph]))

(def g (graph/graph {:root-uri "http://localhost"}))

(g 'out1)

But g is not an high-order function any more. It takes symbol, not function. Personally I don't like this approach. Why don't you like specifying namespace? May be you can do what you need with macro too, but I don't know macros well.

EDIT

Don't do it. Use regular functions as explained by @Ankur and @Gert in comments.




回答2:


Not a direct answer to your question, but the general pattern you're using is more common: having a single stateful data structure that holds connection parameters (to a database or another server). Most frameworks turn this around: instead of calling your functions from within the function holding your connection parameters as you do, they have functions that accept the connection data structure as a parameter.

For example, given a database connetion conn, a typical, fictional database library could look like this (note: examples are simplified for clarity):

(let [conn (make-db-connection :host .... :user ....)]
  (read-from-db conn :user))

While using a library for a messaging framework (say, RabbitMQ) could look like this:

(let [conn (make-amqp-connection :host .... :port ...)]
  (send-message conn :my-queue "hello world"))

In both situations, there is a single conn data structure that is used for all subsequent calls to the libraries' functions. In OO languages, you would have a global, stateful object holding the connection (a singleton perhaps in Java land). In Clojure, libraries typically handle this using a with-... macro, that binds a particular connection to a dynamic var which is used internally:

(with-db-connection (make-db-connection ....)
  (read-from-db :user ....))

(with-rabbit-connection (make-rabbitmq-connection ....)
  (send-message :my-queue "hello world"))

Here's a (fictional) example that implements this pattern. Assume that a connection is a Java object:

;; a var to hold the connection
(def ^:dynamic *current-connection* nil)


(defmacro with-connection [conn & body]
  `(binding [*current-connection* ~conn]
     ~@body))

;; send-msg is using the connection object bound to
;; the *current-connetion* var
(defn send-msg [msg]
  (.sendMessage *current-connection* msg))

;; usage:
(with-connection conn
  (send-msg "hello world!"))

If you want to be fancy, you could support both patterns (accepting a connection as a parameter or using the bound connection) by defining the send-msg function like this:

(defn send-msg [msg & {:keys [connection]
                       :or {connection *current-connection*}}]
  (.sendMessage connection msg))

;; usage with bound connetion:
(with-connection conn
  (send-msg "Hello World!"))

;; usage with explicit connection:
(send-msg "Hello World!"
          :connection conn)

This version of send-msg uses the supplied connection, or the bound connection if a connection wasn't specified.



来源:https://stackoverflow.com/questions/10572443/how-do-you-explicitly-specify-a-namespace-when-using-apply-on-a-function-in-cl

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!