In most code that I've seen the public interface is usually smaller than
the complete implementation. And I don't expect that much metadata to be
attached to private helper functions. But with this change I would have
to. Where I could previously declare my interface in a single expression
I now have to go through my code and inject a #^{:private true} into
many places which is quite inconvenient. I would prefer the export
function instead of this alternative. Any chance to get it back?
-Toralf
Agreed. However if by default everything would be exported unless an
export list is explicitly given, no one would be constrained.
> To your pain point, private fns without metadata otherwise:
>
> (defmacro defn- [name & decls]
> (list* `defn (with-meta name (assoc (meta name) :private true))
> decls))
>
> I'm not sure about the name, but it leverages the last point above to
> reduce, IMO substantially, the effort needed to solve your problem.
> Just add a character to your private defns.
Thanks. I expected a macro to come to my rescue ;-) However I would
either need a more complex one or a couple more to take care of the
other def variants like defstruct, defmacro, def etc. I can live with
that and I understand your points about defaults, but I like the
alternative mentioned above because it feels very straightforward. But
yes, if you plan to set visibility to different levels or do something
else more fine-grained your argument about local vs. global solutions
surely applies.
Thanks,
Toralf
I came up with the following macro to define private scoped vars:
(defmacro private [& defs]
(doseq d# (map (fn* [[def# name# & body#]]
(list* def# (with-meta name# (assoc (meta name#) :private true)) body#))
defs)
(eval d#)))
It takes an arbitrary number of defs and attaches the private tag to its
metadata (based on the example given by Rich). The structural binding
form is very handy I must say. However being relatively new to macros I
would like to know if this can be considered idiomatic or if there is a
better alternative?
Many thanks,
Toralf
I've never before seen a macro with side-effects (of course, in scheme
this is very difficult to do, so perhaps that's why =). Usually macros
are used as code replacement functions, so something like this would
be more idiomatic:
(defmacro private [& defs]
(cons 'do
(map (fn* [[def name & body]]
(list* def
(with-meta name (assoc (meta name) :private true))
body))
defs)))
Useful syntax though, avoids having to define half a dozen more
functions for private versions of things. It doesn't handle the case
where you have a def further inside the code, but that doesn't seem to
happen very often.
Henk
Yeah, I was suspicious of the eval myself :) Thanks for correcting me!
I think this is a good idea. Some modules end up being almost all
exports, but I've written modules which have only one or two exported
values, with all the rest being private. Also, It's easier to catch
when you forget to export something than when you export something you
didn't mean to. Using export makes it harder to make the latter
mistake, and exporting everything as a fallback permits the sort of
brainstorming advantages you gave (which I agree with). For example,
spread and def-aset are now exported from boot.clj. Maybe this is
intentional, but the timing of them becoming exported is suspicious
=).
I suspect that export can be implemented on top of how it currently
works. An export statement could simply use (ns-interns *ns*) and hide
everything not in the list by re-defing them. Of course, something
extra would have to be done in order to permit multiple export
statements, and I'm not sure how to do that. (btw, it seems that
*exports* is still lying around)
Any change of something like this being supported in boot.clj?
Henk
1) To work like it did before.
2) You can put the exports for each section of the file in that section.
3) When temporarily adding something to the exports for debugging
reasons, it's nice to put it next to other debugging code or the
function itself to make it easier to remember to remove it. (If there
a way to break namespace boundaries for debugging this is a non-issue)
None are really that important, I personally wouldn't mind being
limited to one export if it's a big deal to implement, it's just a
restriction I can imagine people complaining about =).
Henk
Of course, I only remember the good reasons after firing off the email...
- If the namespace is separated between multiple files, it would be
nice to have the exports in the file in which they are defined.
- Sometimes it can be useful to write a macro which defines and
exports a number of functions. Here's a snippet from the opengl
bindings I'm working on. It takes the lispy name of a function along
with information about which of its arguments are opengl constants,
and defines/exports the binding for that function.
----
(defmacro def-gl-function [name-v & arg-types]
(let [name (first name-v)
gl-name (or (frest name-v) (symbol (convert-to-gl-name name)))
argsyms (map (fn [x] (gensym 'arg)) arg-types)]
`(do
(defn ~name [~@argsyms]
(. *gl* (~gl-name ~@(map (fn [type symbol]
(if (= type :c)
`(c ~symbol)
symbol))
arg-types argsyms))))
(export '(~name)))))
(defmacro def-gl-function* [& gl-function-exprs]
`(do ~@(map (fn [expr] `(def-gl-function ~@expr))
gl-function-exprs)))
;---- Normal Cases
(def-gl-function*
[[begin] :c]
[[call-list] :d]
[[clear-color] :d :d :d :d]
[[clear-depth] :d]
[[depth-func] :c]
[[disable] :c]
[[enable] :c]
[[end] ]
[[end-list] ]
[[gen-lists] :d]
[[load-identity] ]
[[matrix-mode] :c]
[[new-list] :d :c]
[[ortho] :d :d :d :d :d :d]
[[rotate glRotated] :d :d :d :d]
[[scale glScaled] :d :d :d]
[[translate glTranslated] :d :d :d])
----
In this case it is inconvenient to put all the exports together,
especially later, once I have more than a very small subset of opengl
bound.
Henk
Or perhaps
(defn [list*
:doc "Creates a new list containing the item prepended to more."
:other "more metadata"]
[item & more]
(spread (cons item more)))
Henk