immutable defs?

8 views
Skip to first unread message

Mark

unread,
Oct 2, 2009, 10:29:47 AM10/2/09
to Clojure
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")

Perhaps this is obvious, but I see a lot of discussion of immutable
data structures, but I can't find a way to prevent my bindings from
changing.

Laurent PETIT

unread,
Oct 2, 2009, 11:09:45 AM10/2/09
to clo...@googlegroups.com
 I can think of 'defvar that comes close to what you're about : it does not rebind the root value if it is different from nil

(though it doesn't warn you about the problem, it's more to prevent reinitializing vars when their containing files are reloaded)

2009/10/2 Mark <mjt...@gmail.com>

Meikel Brandmeyer

unread,
Oct 2, 2009, 11:10:53 AM10/2/09
to Clojure
Hi,
I'm not aware of such a feature. Putting {:macro true} in the metadata
of the Var has this effect, but this is almost surely not what you
want.

Why do you need this functionality?

Sincerely
Meikel

Mark Tomko

unread,
Oct 2, 2009, 11:52:03 AM10/2/09
to Clojure
When I write code in Java, I declare everything final that's possible
to be declared final, and I deliberately look for solutions that avoid
reassignment to variables, so all my variables are final).

I'm new to Clojure, so I might be wrong, but it seems that within a
function, mutable bindings must be explicitly created, either through
refs, atoms, or using the 'binding' form (or is it macro?). That's
great, because within any lexical scope inside a function, I can
pretty much count on my bindings to never change.

However, outside the scope of a function, it seems that it's possible
for bindings to be redefined later in a file without causing an
immediate error. This could easily lead to mistakes that would
manifest as silent and potentially inexplicable behavior later.

Mark

Stuart Sierra

unread,
Oct 2, 2009, 1:29:40 PM10/2/09
to Clojure
On Oct 2, 11:52 am, Mark Tomko <mjt0...@gmail.com> wrote:
> However, outside the scope of a function, it seems that it's possible
> for bindings to be redefined later in a file without causing an
> immediate error.  This could easily lead to mistakes that would
> manifest as silent and potentially inexplicable behavior later.

Clojure, like most Lisps, expends less effort to protect programmers
from mistakes than, say, Java.

And for interactive development, it's essential to be able to replace
a function definition.

-SS

John Newman

unread,
Oct 2, 2009, 2:30:42 PM10/2/09
to clo...@googlegroups.com
From what I've seen, people never redef vars in source code.  In general, you shouldn't have to worry about users of your code redefing your vars as it's against common convention, non-idiomatic.

The exception, as Stuart Sierra said, is when writing interactively from the REPL, where you'd like to experiment, replacing nuts and bolts, etc.


John Newman

unread,
Oct 2, 2009, 2:47:51 PM10/2/09
to clo...@googlegroups.com
Also, I'm not sure if your understanding of "binding" is correct.


because within any lexical scope inside a function, I can
pretty much count on my bindings to never change.

binding is actually like a lexical re-def:

user=> (def x 1)
#'user/x

user=> (binding [x 2] (pr x) (binding [x 3] (pr x)) x)
232

user=> x
1

This allows us to confine var re-def to specific scope, safely -- which is the convention over redefing a var globally.

That's my understanding of it at least.  Anyone can feel free to correct me if I'm wrong.

Jonathan Smith

unread,
Oct 2, 2009, 2:48:35 PM10/2/09
to Clojure
I use a let at the top of the file to denote things that I want to
have as captured and constant.

... you can do things like
(let [x 1]
(defn foo-that-uses-x [y]
(function-here x y)))

Mark Tomko

unread,
Oct 2, 2009, 4:15:56 PM10/2/09
to Clojure
That's what I meant when I mentioned the 'binding' form above. The
reason that's okay (to me) is that it explicitly calls out that
bindings may be about to change.

wschnell

unread,
Oct 6, 2009, 11:21:35 AM10/6/09
to Clojure
from the API docs:

(defonce myname "Walter")
; ...
(defonce myname "Schnell")
=> nil
myname
=> Walter

hope this helps ;-)

Travis

unread,
Oct 6, 2009, 7:23:57 PM10/6/09
to Clojure
+1

In my relatively novice opinion, unless there is a reason to make
functions and vars available to code executing in a *different*
namespace, there isn't a lot of reason to def anything at all.

Stephen C. Gilardi

unread,
Oct 6, 2009, 9:14:05 PM10/6/09
to clo...@googlegroups.com
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.

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

rzez...@gmail.com

unread,
Oct 6, 2009, 9:40:50 PM10/6/09
to Clojure
On Oct 2, 11:52 am, Mark Tomko <mjt0...@gmail.com> wrote:

> However, outside the scope of a function, it seems that it's possible
> for bindings to be redefined later in a file without causing an
> immediate error.  This could easily lead to mistakes that would
> manifest as silent and potentially inexplicable behavior later.
>


There is a reason def can mutate things at the global root. See
2:24:00 of the Clojure Concurrency screencast [1].

Basically, you need this ability in order to fix/change programs
without restarting them.

1: http://clojure.blip.tv/file/812787/

Mark Tomko

unread,
Oct 7, 2009, 12:47:03 AM10/7/09
to Clojure
This is pretty much what I'd had in mind. 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?
>  smime.p7s
> 3KViewDownload

John Harrop

unread,
Oct 7, 2009, 1:53:20 AM10/7/09
to clo...@googlegroups.com
On Tue, Oct 6, 2009 at 9:14 PM, Stephen C. Gilardi <sque...@mac.com> wrote:
    `(do (set-validator! (defvar ~name ~init) #{~init}) (var ~name)))

Cute hack. Won't work if init is false or nil, though, unless the validator does not trigger on the initial assignment of the value. 

Meikel Brandmeyer

unread,
Oct 7, 2009, 5:19:14 AM10/7/09
to Clojure
Hi,

On Oct 7, 7:53 am, John Harrop <jharrop...@gmail.com> wrote:
> On Tue, Oct 6, 2009 at 9:14 PM, Stephen C. Gilardi <squee...@mac.com> wrote:
>
> >     `(do (set-validator! (defvar ~name ~init) #{~init}) (var ~name)))
>
> Cute hack. Won't work if init is false or nil, though, unless the validator
> does not trigger on the initial assignment of the value.

One could make it really constant via:

(set-validator! (defvar ~name ~init) (constantly false))

Or is setting the validator calling it on the already set value?

Sincerely
Meikel

Laurent PETIT

unread,
Oct 7, 2009, 6:44:44 AM10/7/09
to clo...@googlegroups.com


2009/10/7 Stephen C. Gilardi <sque...@mac.com>
Hello,

~init twice in the 2 args arity version of the macro

Laurent PETIT

unread,
Oct 7, 2009, 6:47:37 AM10/7/09
to clo...@googlegroups.com
2009/10/7 Stephen C. Gilardi <sque...@mac.com>
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.

Harmless to the semantics of it being constant, yes, but not the semantics of it having to be defined just once ( and not having to be "tried" to be rebound ).
e.g. if the new binding value is dynamic, the program may or may not work depending on the value ...

I prefer Meikel's approach : the root binding is set once and for all, and that's the contrat.

Stephen C. Gilardi

unread,
Oct 7, 2009, 1:06:42 PM10/7/09
to clo...@googlegroups.com

On Oct 7, 2009, at 5:19 AM, Meikel Brandmeyer wrote:

Or is setting the validator calling it on the already set value?

Yes, the validation mechanism calls the validator on the already set value.

--Steve

Stephen C. Gilardi

unread,
Oct 7, 2009, 1:26:57 PM10/7/09
to clo...@googlegroups.com

On Oct 7, 2009, at 12:47 AM, Mark Tomko wrote:

> 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

Reply all
Reply to author
Forward
0 new messages