Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you organize function names when building clojure libraries for public consumption?

Let's say I want to build a large clojure library with several components. As a developer, I would like to keep many of the components in separate namespaces since many helper functions can have similar names. I don't necessarily want to make things private since they may have utility outside in extreme cases and the work-arounds behind private is not good. (In other words, I would like to suggest code usage, not completely prevent usage.)

However, I would like the users of the library to operate in a namespace with a union of a subset of many of the functions in each sub library. What is the idiomatic or best way to do this? One solution that comes to my mind is to write a macro that generates :requires and creates a new var mapping by def'ing a given list of var names (see first code example). Are there tradeoffs with this method such as what happens to extending types? Is there a better way (or builtin)?

Macro example (src/mylib/public.clj):

 (ns mylib.public
    (:require [mylib.a :as a])
    (:require [mylib.b :as b]))

 (transfer-to-ns [+ a/+
                  - b/-
                  cat b/cat
                  mapper a/mapper])

Again, to clarify, the end goal would be to have some file in other projects created by users of mylib to be able to make something like (src/someproject/core.clj):

 (ns someproject.core
     (:require [mylib.public :as mylib]))

 (mylib/mapper 'foo 'bar)

@Jeremy Wall, note that your proposed solution does not fullfill my needs. Lets assume the following code exists.

mylib/a.clj:

 (ns mylib.a)

 (defn fa [] :a)

mylib/b.clj:

 (ns mylib.b)

 (defn fb [] :b)

mylib/public.clj:

 (ns mylib.public
     (:use [mylib.a :only [fa]])
     (:use [mylib.b :only [fb]]))

somerandomproject/core.clj: (Assume classpaths are set correctly)

 (ns somerandomproject.core
     (:require [mylib.public :as p])

 ;; somerandomproject.core=> (p/fa)
 ;; CompilerException java.lang.RuntimeException: No such var: p/fa, compiling:     (NO_SOURCE_PATH:3) 
 ;; somerandomproject.core=> (mylib.a/fa)
 ;; :a

If you notice, "using" functions in mylib/public.clj DOES NOT allow public.clj to PROVIDE these vars to the user file somerandomproject/core.clj.

like image 815
bmillare Avatar asked Jul 30 '11 04:07

bmillare


2 Answers

You might find it interesting to look at how a library like Compojure or Lamina organizes its "public" api.

Lamina has "public" namespaces like lamina.api that serve to alias (using Zach's import-fn from his potemkin library) functions from the "internal" namespaces like lamina.core.pipeline. With a bit of docs, this serves to clearly delineate the public-facing ns's from the likely-to-change internals. I've found the major drawback to this strategy is that the import-fn makes it much more difficult to walk (in emacs) from the use of a function into it's implementation. Or to know which function to use clojure.repl/source on for example.

A library like Compojure uses private vars to separate public/private parts of the functions (see compojure.core for example). The major drawback with "private" functions is that you may later find you want to expose them or that it makes testing more complicated. If you control the code base, I don't find the private aspects to be a big deal. The testing issue is easily worked around by using #'foo.core/my-function to refer to the private function.

Generally I tend to use something more like Compojure's style than Lamina's but it sounds like you'd prefer something more like Lamina.

like image 184
Alex Miller Avatar answered Oct 19 '22 04:10

Alex Miller


I'm not exactly sure what your asking here. I think maybe you want to know what the best practice for importing publics from a namespace for shared utility functions? In that case the refer function is what you are looking for I think: http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/refer

(refer mylib.a :only [+])
(refer mylib.b :only [-])

It imports the public items in a namespace into the current namespace. However the preferred method would be to do this in your namespace declaration with a :use directive

(ns (:use (mylib.a :only [+])
          (mylib.b :only [-])))
like image 42
Jeremy Wall Avatar answered Oct 19 '22 06:10

Jeremy Wall