Copying (immigrating) macros from namespace to namespace

563 views
Skip to first unread message

the80srobot

unread,
Jan 6, 2013, 3:19:44 PM1/6/13
to clo...@googlegroups.com
I have come up with a solution to a problem I don't think exists outside of my mind, but since I can't for the life of me figure out how Clojure 'wants' me to do this, I thought I would bounce this off the Google Group.

The scenario: I am trying to collect a bunch of functions and macros from all the different namespaces in my library into a single namespace; let's call the namespace adams-lib.api. The idea is that the consumer of the library will only have to include that one API namespace, and not all of the little namespaces with parts of the functionality.

The problem: Obviously, calling (use) doesn't cut it, because the aliased vars are not immigrated by subsequent calls to the (use) of the namespace that (used) them. You know what I mean.

The "solution": For functions this is simpler, but macros pose some additional challenges, which is why I'm including my "solution" for immigrating macros. Hold on to your hats, this one is ugly:

(defmacro immigrate-macro
  [ns-sym macro-sym]
  `(let [ nspublics# (ns-publics '~ns-sym)
          macro-var# (nspublics# '~macro-sym)
          macro-fn# @macro-var#
          macro-meta# (meta macro-var#)]
    (def ~macro-sym macro-fn#)
    (. (var ~macro-sym) (setMacro))
    (reset-meta! (var ~macro-sym) macro-meta#))
    nil)

And using it:

(immigrate-macro adams-lib.some-other-ns some-macro)

There has got to be a simpler/better/less insane way of doing this. Would anyone care to weigh in? For the record, I fully realize that my solution is not OK.

-Adam

Sean Corfield

unread,
Jan 6, 2013, 4:34:22 PM1/6/13
to clo...@googlegroups.com
Here's what I use to pull symbols from Enlive into FW/1:

(def ^:private enlive-symbols
['append 'at 'clone-for 'content 'do-> 'html-content 'prepend
'remove-class 'set-attr 'substitute])

(defmacro enlive-alias ^:private [sym]
`(let [enlive-sym# (resolve (symbol (str "html/" ~sym)))]
(intern *ns* (with-meta ~sym (meta enlive-sym#)) (deref enlive-sym#))))

This pulls in both functions and macros. It hard-codes the alias for
Enlive (html, for net.cgrand.enlive-html) but otherwise should serve
your needs. FW/1 uses it like this:

(doseq [sym enlive-symbols]
(enlive-alias sym))

Sean
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en



--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Brian Marick

unread,
Jan 6, 2013, 8:04:13 PM1/6/13
to clo...@googlegroups.com

On Jan 6, 2013, at 3:34 PM, Sean Corfield <seanco...@gmail.com> wrote:

> Here's what I use to pull symbols from Enlive into FW/1:


Midje plays similar tricks to make namespace abilities available via one `use`.

Which makes me think:

1: In the old patterns world, there was a "rule of three" which claimed that something shouldn't be published as a pattern until it could be demonstrated in three real systems.

2: Lispers like Gabriel and others noted that what were patterns in languages like Smalltalk and C++ were built into Lisp or were easily regularized in-language with macros.

Therefore: to me, this email thread suggests that the ability to do such consolidation should be immortalized not in email examples of patterns ("here's how I accomplished X") but - in a more Lispy fashion - by writing a common library and making it available to the user base. That is: functions like Adam's and Sean's and mine should be in some official library close to clojure.core.

--------
Occasional consulting on programming technique
Contract programming in Ruby and Clojure
Latest book: /Functional Programming for the Object-Oriented Programmer/
https://leanpub.com/fp-oo

Leonardo Borges

unread,
Jan 6, 2013, 8:27:59 PM1/6/13
to clo...@googlegroups.com
+1

I was thinking of doing the same in the validation lib I published
recently [1] and this thread came in handy. I haven't implemented it
yet but having a common way in which people do this seems reasonable -
if it's in core then all the better.

Cheers,
Leo


[1] - https://github.com/leonardoborges/bouncer
Leonardo Borges
www.leonardoborges.com

Stuart Sierra

unread,
Jan 6, 2013, 9:17:55 PM1/6/13
to clo...@googlegroups.com
I've said it before and I will keep saying it: copying symbols by interning vars breaks the identity of the original vars. It breaks dynamic binding, with-redefs, and the ability to redefine functions at the REPL.

Clojure has a two perfectly good mechanisms for making vars available in other namespaces:

1. `refer`. Define a public function that `refer`s all the symbols you want. It's an extra step for the user, but that's good because it makes it evident that extra symbols are being added.

2. `load`. If you have N namespaces with no clashing symbols, then you only need one namespace. If you want to split it across multiple files, use `load`. `clojure.core` does this.

-S

Leonardo Borges

unread,
Jan 6, 2013, 9:24:53 PM1/6/13
to clo...@googlegroups.com
On Mon, Jan 7, 2013 at 1:17 PM, Stuart Sierra
<the.stua...@gmail.com> wrote:
> I've said it before and I will keep saying it: copying symbols by interning
> vars breaks the identity of the original vars. It breaks dynamic binding,
> with-redefs, and the ability to redefine functions at the REPL.
>
> Clojure has a two perfectly good mechanisms for making vars available in
> other namespaces:
>
> 1. `refer`. Define a public function that `refer`s all the symbols you want.
> It's an extra step for the user, but that's good because it makes it evident
> that extra symbols are being added.
>

So this would be no different than having the user put an extra 'use'
call to use any additional namespaces right? In this case I'd rather
have the user explicitly perform the "use" call instead of "refer",
like this:

(require '[bouncer.core :as b])
(use '[bouncer.validators :only [defvalidator])

Which is what I recommend at the moment.

> 2. `load`. If you have N namespaces with no clashing symbols, then you only
> need one namespace. If you want to split it across multiple files, use
> `load`. `clojure.core` does this.
>

This sounds like what I want. I'll look into it, thanks.

Leo.

Sean Corfield

unread,
Jan 6, 2013, 9:42:53 PM1/6/13
to clo...@googlegroups.com
On Sun, Jan 6, 2013 at 6:17 PM, Stuart Sierra
<the.stua...@gmail.com> wrote:
> 1. `refer`. Define a public function that `refer`s all the symbols you want.
> It's an extra step for the user, but that's good because it makes it evident
> that extra symbols are being added.

Forcing all users of a library to refer a whole slew of symbols from
another library that they don't even know they're using is horrible
usability and really not a useful suggestion. The user should be able
to (:require [main-library :refer [what i want]) without being forced
to know about a transitive dependency and know which symbols come from
which library, if the main-library developer wishes to provide a
single, simple API.

Could you explain exactly how this "breaks dynamic binding,
with-redefs, and the ability to redefine functions at the REPL" given
the scenario we're discussing? Remember that the whole purpose of this
discussion is to essentially hide the underlying namespace from which
symbols originate, and to present a single namespace with the desired
API.

> 2. `load`. If you have N namespaces with no clashing symbols, then you only
> need one namespace. If you want to split it across multiple files, use
> `load`. `clojure.core` does this.

Fine if you control all of the source yourself.

In the case of FW/1, I want a portion of Enlive available directly and
easily to users of FW/1. Enlive is a transitive dependency - users of
FW/1 don't need to know about it, and shouldn't really care.

Since this appears to be a relatively common use case for library
developers, perhaps a native Clojure solution that doesn't have the
downsides you list would be worth adding? Perhaps some sort of (export
some-ns/some-symbol) that makes *ns*/some-symbol appear like an exact
public alias of some-ns/some-symbol? So users of the library see those
exported symbols exactly as if they were declared in the library
itself, rather than the third-party dependency, and could interact
with them as such.

Vedang

unread,
Jan 7, 2013, 1:30:49 AM1/7/13
to clo...@googlegroups.com
I remembered bookmarking a library that did something similar, so I
looked at my bookmarks. Here is Zach Tellman's "potemkin" library, you
might be interested in checking it out:
https://github.com/ztellman/potemkin

From the README :

"Clojure’s namespaces conflate how you implement your code and how
it’s consumed by other code. This isn’t always a bad thing, but in
practice this means that large projects either have surprisingly large
source files (e.g. clojure.core) or a surprising number of namespaces
that have to all be used in concert to accomplish complex tasks (e.g.
Ring).

The former approach places an onus on the creator of the library; the
various orthogonal pieces of his library all coexist, which can make
it difficult to keep everything straight. The latter approach places
an onus on the consumers of the library, forcing them to remember
exactly what functionality resides where before they can actually use
it.

import-fn and import-macro decouple the structure of the code from the
structure of the API, allowing the library creator to structure the
code however he likes, without necessarily requiring that the
consumers have the same intimate understanding of that structure.

As someone who spends a fair amount of time worrying both about the
structure of the code and the presentation of the API, it’s helpful
not to have to think about how one affects the other. You might find
it helpful, too."

/vedang
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en



--
Unix is simple. It takes a genius to understand it's simplicity. - Anon
People think it must be fun to be a super genius, but they don't
realize how hard it is to put up with all the idiots in the world.
- Calvin.

Cheers,
Vedang.

Programmer,
Infinitely Beta.
http://twitter.com/vedang
http://vedang.me

the80srobot

unread,
Jan 7, 2013, 5:39:03 AM1/7/13
to clo...@googlegroups.com
Stuart,

You're right that it breaks identity. How about a different approach then - I can get the list of referred vars from a namespace, and vars can cary metadata. A natural way to add more flexibility would be to add a flag - let's call it :export - to referred vars that I'd like for namespaces that subsequently use mine to also refer. A var marked with :export would effectively propagate through any number of calls to (use).

I've thrown together a quick POC that seems to work (although it's obviously limited and far from optimal in how it works). The full code of the example is on Gist. Ideally, I'd like to have either (use) or (refer) propagate :export'd vars in a similar fashion.

Can you see problems with this?

- Adam
Reply all
Reply to author
Forward
0 new messages