because within any lexical scope inside a function, I can
pretty much count on my bindings to never change.
> Is there a way to make a declaration in Clojure that cannot be rebound
> later? Ideally, I'd like something that fails if I try to do this:
>
> (def myname "mark")
> ; ...more code, elided...
> (def myname "Mark")
Along these lines, I was thinking of adding defconst to
clojure.contrib.def. It's like Common Lisp's defconst, but the fact
that its value isn't allowed to change is enforced:
(defmacro defconst
"Defines a var with a constant value and optional doc string. Any
attempt
to redefine, bind, or set! the const to a different value will
throw an
exception."
([name init]
`(do (set-validator! (defvar ~name ~init) #{~init}) (var ~name)))
([name init doc]
`(defconst ~(with-meta name (assoc (meta name) :doc doc)) ~init)))
It does allow a redefine, binding, or set! to an equal value mainly as
an artifact of its implementation, but also because such a "change"
would be harmless to the semantics of it being constant.
user=> (defconst pi (* 4 (Math/atan 1)))
#'user/pi
user=> pi
3.141592653589793
user=> (binding [pi 3] (prn pi))
java.lang.IllegalStateException: Invalid reference state
(NO_SOURCE_FILE:0)
user=> (binding [pi pi] (prn pi))
3.141592653589793
nil
user=> (binding [pi pi] (set! pi 3))
java.lang.IllegalStateException: Invalid reference state
(NO_SOURCE_FILE:0)
user=> (defconst pi 3)
java.lang.IllegalStateException: Invalid reference state
(NO_SOURCE_FILE:7)
user=> (def pi 3)
java.lang.IllegalStateException: Invalid reference state
(NO_SOURCE_FILE:10)
user=> (defconst pi (* 4 (Math/atan 1)))
#'user/pi
I'd appreciate hearing any suggestions for improvement or other
feedback.
--Steve
`(do (set-validator! (defvar ~name ~init) #{~init}) (var ~name)))
On Oct 2, 2009, at 10:29 AM, Mark wrote:
Is there a way to make a declaration in Clojure that cannot be rebound
later? Ideally, I'd like something that fails if I try to do this:
(def myname "mark")
; ...more code, elided...
(def myname "Mark")
Along these lines, I was thinking of adding defconst to clojure.contrib.def. It's like Common Lisp's defconst, but the fact that its value isn't allowed to change is enforced:
(defmacro defconst
"Defines a var with a constant value and optional doc string. Any attempt
to redefine, bind, or set! the const to a different value will throw an
exception."
([name init]
`(do (set-validator! (defvar ~name ~init) #{~init}) (var ~name)))
([name init doc]
`(defconst ~(with-meta name (assoc (meta name) :doc doc)) ~init)))
It does allow a redefine, binding, or set! to an equal value mainly as an artifact of its implementation, but also because such a "change" would be harmless to the semantics of it being constant.
Or is setting the validator calling it on the already set value?
> This is pretty much what I'd had in mind.
Thanks for the comments and suggestions all.
> I don't see how the text of
> the Exception is set by the macro, but it'd be really spectacular if
> the message were more clear. Is that message coming from the defvar
> form?
The text of the exception was the default one when a validator (http://clojure.org/api#toc518
) returns logical false. It's customizable by throwing a
RuntimeException rather than returning logical false.
Here's a new version which addresses this and the other feedback so far:
(defn const-validator
"Returns a validator that ensures the value of a const never changes"
[const]
(fn [x]
(or (= x @const)
(throw
(UnsupportedOperationException.
(str "the value of const " const " cannot be modified"))))))
(defmacro defconst
"Defines a const: a var whose value is constant. Any attempt to
redefine,
bind, or set! the const to a different value will throw an
exception."
([name value]
`(do
(defvar ~name ~value)
(set-validator! (var ~name) (const-validator (var ~name)))
(var ~name)))
([name value doc]
`(defconst ~(with-meta name (assoc (meta name) :doc doc))
~value)))
With examples:
user=> (defconst e (Math/exp 1))
#'user/e
user=> e
2.7182818284590455
user=> (def e 4)
java.lang.UnsupportedOperationException: the value of const #'user/e
cannot be modified (NO_SOURCE_FILE:4)
user=> (binding [e 4] (prn e))
java.lang.UnsupportedOperationException: the value of const #'user/e
cannot be modified (NO_SOURCE_FILE:0)
user=> (binding [e e] (prn e))
2.7182818284590455
nil
user=> (binding [e e] (set! e 4))
java.lang.UnsupportedOperationException: the value of const #'user/e
cannot be modified (NO_SOURCE_FILE:0)
user=> (defconst my-false false)
#'user/my-false
user=> my-false
false
user=> (def my-false 3)
java.lang.UnsupportedOperationException: the value of const #'user/my-
false cannot be modified (NO_SOURCE_FILE:10)
user=> (defconst my-nil nil)
#'user/my-nil
user=> my-nil
nil
user=> (defn my-nil [x] (+ 3 x))
java.lang.UnsupportedOperationException: the value of const #'user/my-
nil cannot be modified (NO_SOURCE_FILE:13)
user=>
--Steve