tags:

views:

122

answers:

2

Would it be possible to override the 'require' command so that it will try to download a certain resource if it was not found on the local machine. For example:

(require 'examples.introduction) 
; if not found => download from the net
;   (url: http://media.pragprog.com/titles/shcloj/code/examples/introduction.clj)
+3  A: 

You could override the require function and of course the overriden variant could download stuff if the namespace it was asked for is not available on the classpath. Overriding the way :require works in (ns ...) forms is, AFAIK, impossible for now due to the way in which ns is handled.

Note that such a 'downloading require' wouldn't be very helpful if you wanted to place new paths on the classpath (including new jars), as classpath injection doesn't work reliably in Clojure (due to JVM issues). There is clojure.core/add-classpath... but it's been marked as deprecated since forever now, its use is strongly discouraged, there are no guarantees that it will work for you and this situation isn't likely to change anytime soon. On the other hand, if you wanted to put new source files in a directory which was already present on the classpath, then that should work fine.

In case you do want to play around with overriding require, if you have a foo namespace, you could do

(ns foo
  (:refer-clojure :exclude [require])
  ; other stuff; any :requires here will work as usual!
  )

Then define your own require, using clojure.core/require when appropriate:

(defn require [ns-symbol]
  (do-stuff-to-obtain-the-namespace))

clojure.contrib.find-namespaces namespace might be helpful in finding out what's available on the classpath. (Or you could use the the-ns function and see if it throws an exception after an initial attempt at requiring the namespace through clojure.core/require.)

Note that the binding approach which might come to mind first ((binding [require ...] ...)) will not work, since require normally resolves to a Var interned in the clojure.core namespace and Vars from namespaces whose names start with clojure are currently directly linked by the compiler (meaning no actual Var lookup is performed at runtime, so rebinding of those Vars has no effect on the code).

The (:refer-clojure :exclude [require]) in the ns form for your namespace prevents require from resolving to clojure.core/require and leaves you free to define a Var of that name in your own namespace. As mentioned above, that doesn't prevent the clojure.core/require Var from being accessible if you type out the fully qualified the symbol.

Michał Marczyk
Interesting. I'm thinking it may be better to use a different function name like "enhanced-require" in order to avoid using the namespace workarounds. (I'm a total noob, so correct me if I'm speaking nonsense. :))
StackedCrooked
On the contrary, that seems like a better way to go about it. I have to say, though, that the *best* way, in practical terms, would be to have Leiningen / Maven get any library dependencies, and for things like source code accompanying books, just go and download it by hand... :-) Not that it takes anything out of the fun of writing a funky require which would do that by itself!
Michał Marczyk
+1  A: 

Actually, (add-classpath "http://foo/bar/baz/src/") or (add-classpath "http://www.foo.com/bar.jar"), will allow requiring remote stuff.

Michał's warnings do apply though: Only use this for toying at the repl ...

danlei