Hacker News new | comments | show | ask | jobs | submit login

One of my favorite things about Clojure is the things it's said "yes" to, as first-class language builtins: char, vector, set, and map notation; first class "vars" (actually thread locals), ways to manage and dereference state, Java interop that doesn't set your hair on fire, namespaces, keywords and namespaced keywords, and a whole bunch of features I'm probably forgetting.

Instead, Steve Yegge is asking for things that don't seem terribly important to me. Excluding loops from the language core is obvious; Clojure makes it really easy to use the functional style instead and loops would serve as a newbie trap. For the last two weeks I've been programming Clojure full-time and haven't used a single loop macro or loop-recur. He complains about the lack of circular dependencies; but no circular dependencies across namespaces is A Good Thing just as you shouldn't have circular dependencies across Java packages or C++ libraries. The guy he cites who declared macros evil is obviously not a part of 'mainstream' Clojure culture, and maybe someone can explain Yegge's anger about single-pass compilation, because I don't get it.

And of course, Clojure is a highly extensible language that has implicitly said Yes to a vast ideascape.




> The guy he cites who declared macros evil is obviously not a part of 'mainstream' Clojure culture

I think Yegge was referring to Christophe Grand's talk on DSL's and macros. Grand has written several notable Clojure libraries, so I do consider him part of 'mainstream' Clojure culture. But he didn't say "macros are dangerous and you should never use them". Quoting Phil Hagelberg's response in the linked discussion:

  The talk I saw was about how functions are generally more
  composable than macros, so they should be the first tool
  you reach for. I think it was more directed at folks who
  come to lisp and are drunk with the power of macros.
 
  (Assuming it was this talk: http://clojure.blip.tv/file/4522250/)
Towards the end of the talk, Grand discusses how macros can be useful for optimizing or adding syntactic sugar to DSL's.


This is not a "Clojure thing."

The advice to noobs not to go hog wild with macros is common among experienced Lisp programmers in all dialects. Paul Graham himself refers to it in _On Lisp_: Chapter 8 is titled "When to Use Macros" and the first section is "When Nothing Else Will Do."


I think the issue with single-pass compilation is that you can't have a function a in namespace A call a function b in namespace B which in turn calls a, unless you've first done a forward declaration of the var(s). I can't see how the hell 2-pass compilation can be reconciled with Clojure's macro system; either Steve or I have not thought this one through. Personally, I've run into this once [1] and it was easy to resolve with an

  (ns OTHER-NAMESPACE) 
  (def missing-function)
At the top of the file. I'm used to worse than that from other languages, mind. (cough C++ cough)

I don't get what the fuss is about, either. The debugger is pretty much the only suggestion I like - I've used JSwat with Clojure and it's not very pleasant. Obviously some kind of continuation-based debugging facility would be nice, but it's basically not going to happen because of the JVM, not because Rich Hickey is an Evil Bastard (he's not). Either way, a usable debugger will materialise sooner or later, Yegge-tantrum or not.

I think Clojure is being managed very well. I admittedly haven't followed the community for a while; the mailing list got a bit too high traffic for me, and I can't use the JVM for most of my projects. But whenever I am using Clojure and have a problem, it's usually because I haven't found the right bit in the docs, or because I missed/misunderstood a major feature that was added recently, or because the functional style sometimes is just a bit beyond the capabilities of my brain. (I'll write the code in imperative style, then translate it to functional, and usually learn something in the process; done) I don't see that as a failing of the language, but my inexperience with it. Doing simple things with it is simple.

I have a theory, though. Yegge talks about porting Java code to Clojure. I think that's the problem right there. Sure, if you're porting line-by-line, it's not going to be especially nice. It's a bit like translating German to English sentence-by-sentence. Most of the time it works, despite sounding a bit stilted. But sometimes there isn't a direct equivalent, and you have to change the surrounding sentences. The structure of your average Java program is so wildly different of your average Clojure program, no wonder it doesn't port directly.

[1] Okay, I'm not massively experienced with Clojure. I've written maybe a few tens of thousands of LOC of it, and no really big projects.


I can relate to the debugging part. Useless stack traces have been the reason I simply gave up on Clojure.


I wouldn't call them useless, but I guess I'm used to C++, or worse, just a hex stack dump (yay, game consoles). Clojure mangles names somewhat to make them Java-compatible; you can easily do the reverse translation in your head. Still, this could be automated rather easily.


As far as I remember clojure just spit out "This is not a function" or something like that, without any indication where the error occured.

There is a trace macro but it doesn't provide complete traces without explicitly adding every function you want to trace, which becomes tricky very soon. Manual tracing with printf()s like in non-functional languages isn't built-in either. There are half-baked macros for this on blogs if you google hard enough, but that is not the kind of thing that leaves a solid impression of a language.

Maybe I was just working in a non-clojuresque way.


I agree its a problem. I use the repl a lot and bild up the functions and debug with prints witch is ok because you can print anything (not like in VB). I know that they are working on it so I hope it will get better.

For me, however, its not a reason to quit.


I have been working on a fork of Clojure off-and-on that tries to improve the debugging experience (https://github.com/qbg/clojure). How do you think the stack traces could be improved?


This is one example of how to remove uninteresting stack frames from Clojure stack traces. http://j.mp/h3xzdN



Suppose you accidentally put an extra pair of braces deeply inside a function. The message you'll get is something like "could not cast to function" without any meaningful information where this happened.


I'm astonished to discover Clojure does do single-pass compilation. It used to be done primarily because some intermediate representations of programs could not fit entirely in memory, but virtually every compiler now is multi-pass because so many optimization opportunities are lost otherwise.

I have minimum knowledge of Clojure, so is there some motivation for single-pass other than perhaps simplicity?

Edit: A brief outline of the tradeoffs: http://en.wikipedia.org/wiki/Compiler#One-pass_versus_multi-...


The issue is not single-pass vs multi-pass. It is instead, what constitutes a compilation unit, i.e., a pass over what?

Clojure, like many Lisps before it, does not have a strong notion of a compilation unit. Lisps were designed to receive a set of interactions/forms via a REPL, not to compile files/modules/programs etc. This means you can build up a Lisp program interactively in very small pieces, switching between namespaces as you go, etc. It is a very valuable part of the Lisp programming experience. It implies that you can stream fragments of Lisp programs as small as a single form over sockets, and have them be compiled and evaluated as they arrive. It implies that you can define a macro and immediately have the compiler incorporate it in the compilation of the next form, or evaluate some small section of an otherwise broken file. Etc, etc. That "joke from the 1980's" still has legs, and can enable things large-unit/multi-unit compilers cannot. FWIW, Clojure's compiler is two-pass, but the units are tiny (top-level forms).

What Yegge is really asking for is multi-unit (and larger unit) compilation for circular reference, whereby one unit can refer to another, and vice versa, and the compilation of both units will leave hanging some references that can only be resolved after consideration of the other, and tying things together in a subsequent 'pass'. What would constitute such a unit in Clojure? Should Clojure start requiring files and defining semantics for them? (it does not now)

Forward reference need not require multi-pass nor compilation units. Common Lisp allows references to undeclared and undefined things, and generates runtime errors should they not be defined by then. Clojure could have taken the same approach. The tradeoffs with that are as follows:

1) less help at compilation time 2) interning clashes

While #1 is arguably the fundamental dynamic language tradeoff, there is no doubt that this checking is convenient and useful. Clojure supports 'declare' so you are not forced to define your functions in any particular order.

#2 is the devil in the details. Clojure, like Common Lisp, is designed to be compiled, and does not in general look things up by name at runtime. (You can of course design fast languages that look things up, as do good Smalltalk implementations, but remember these languages focus on dealing with dictionary-carrying objects, Lisps do not). So, both Clojure and CL reify names into things whose addresses can be bound in the compiled code (symbols for CL, vars for Clojure). These reified things are 'interned', such that any reference to the same name refers to the same object, and thus compilation can proceed referring to things whose values are not yet defined.

But, what should happen here, when the compiler has never before seen bar?

    (defn foo [] (bar))
or in CL:

    (defun foo () (bar))
CL happily compiles it, and if bar is never defined, a runtime error will occur. Ok, but, what reified thing (symbol) did it use for bar during compilation? The symbol it interned when the form was read. So, what happens when you get the runtime error and realize that bar is defined in another package you forgot to import. You try to import other-package and, BAM!, another error - conflict, other-package:bar conflicts with read-in-package:bar. Then you go learn about uninterning.

In Clojure, the form doesn't compile, you get a message, and no var is interned for bar. You require other-namespace and continue.

I vastly prefer this experience, and so made these tradeoffs. Many other benefits came about from using a non-interning reader, and interning only on definition/declaration. I'm not inclined to give them up, nor the benefits mentioned earlier, in order to support circular reference.

Rich


Most of the user annoyance vanishes if declare were to support qualified names: (declare other-namespace/symbol). Is there a technical limitation here?

-steve


One problem is what to do if the other-namespace doesn't already exist. It would have to be created, and that initialization is unlikely to be the same as the declared one. Possibly follow-on effects when it is subsequently required. The other option, with similar issues, is to allow fully qualified references to non-existent vars in code.


If it doesn't already exist, then you issue a compilation error. Better then than the way it works today, IMO.


Don't sign comments. Your username is visible.


CL is a language. Not an implementation. One may need to differentiate between a language and an implementation. CL is defined so that different implementation strategies are possible.

CL itself has two different compilation interfaces COMPILE and COMPILE-FILE. A file compiler may implement a multipass strategy, where calls to later in that file provided functions are resolved.

A compiler may also provide different kinds of compilation units. IIRC CMUCL does that. See "block compilation".


Thank you, Rich, very informative.

What is the rationale behind not letting 'declare' declare vars in other namespaces? I've run into that when dealing with circular dependencies, and sometimes it seems that would help.


Such declarations are possible (as would be accepting fully-qualified references to not-yet-existing things), but the devil's in the details again - e.g. what if the other ns doesn't yet exist?

And the complexity/utility tradeoffs must be considered.


"CL happily compiles it, and if bar is never defined, a runtime error will occur. ... You try to import other-package and, BAM!, another error - conflict, other-package:bar conflicts with read-in-package:bar. Then you go learn about uninterning.

fwiw, you get a warning at compile time regarding the undefined symbol. And in the AllegroCL IDE, for example, uninterning is just a matter of hitting [Return] when you get the error message (dialog). I believe uninterning the conflicting symbol is a common restart in other implementations as well.


"what constitutes a compilation unit, i.e., a pass over what?"

So, how is this compilation unit different from all other compilation units?


One way is that Rich's example compilation units (forms input at a REPL) don't all exist at the same time, like compilation units that are files often do.


Consider how much powerful macros can be if you don't have to deal with out of order definitions.


Macros are wonderful, but intuitively it seems very limited in scope wrt compile-time optimizations.

For example, how would you do dataflow analysis at compile-time (which is necessary for a plethora of optimizations) with just macros?


JVM does that for you. However you're free to do all sorts of kinds of optimizations that the JVM simply can't do. Think about a in-memory logic engine that's running during compilation - you can introduce relations which influence how code further down the line is compiled via macros.


Please explain?


Christophe Grand is very much a part of mainstream Clojure culture. He is currently co-authoring a book on Clojure. Furthermore, he never said that macros are evil and should never be used. I'm tempted to say that Steve may have never even watched the talk in the first place after reading his comments. I was there when that talk was made, and I did not even remotely get the feeling that he was telling me that macros were evil and should never be used. That is totally missing the point.


I am very new to Clojure and only semi-experienced in CL, but I ran into a problem today that left a pretty bad taste in my mouth. I had something like:

  (defn foo [bar]
    (let [whatnot (+ bar 3)]
      (frobnicate whatnot)))
  
  (defn frobnicate [quux]
    (println "Hello" quux))

And it threw an error, since frobnicate hadn't been defined while it was dealing with foo. But I didn't know that. And using and commenting out my (ns spam.core)--and trying various compilation incantations with SLIME--were confounding variables such that the call to frobnicate sometimes worked if the ns call was removed and you held your nose right. Finally I gave up and posted to Stack Overflow, thinking I'd jacked up my namespace somehow. Instead they told me to either move frobnicate's definition to be above foo, or else declare it.

It never occured to me that a modern, not-low-level well-received language would have this limitation. And I feel like I'm part of the target demographic for Clojure. I'm an experienced Java and Python coder. I know and like Lisp. I'm interested in learning a new language. And I did what I thought I was supposed to--I read the list of differences between Clojure and other Lisps on the official site. I read a tutorial that concisely addressed Clojure syntax, data structures, special forms and so forth in a way designed for people who already knew how to program.

And there's a lot of things I like--better data structures, the lambda reader macro, the judicious use of brackets to avoid paren soup, Java interop, and more things that I'm only discovering as I gain more experience.

But I'm still kind of in shock about having to declare methods. A completely unexpected wart.


There are two warts - having to declare methods, and the obscure error message. A smart error handler could have seen that frobnicate had been defined further down, and told you exactly how to fix it. And it would be ok when that error handler didn't work in the REPL.


Agreed. Here's the exact error message I got:

  error: java.lang.Exception: Unable to resolve symbol: frobnicate in this context
So I really thought I'd misspelled it or messed up my namespace or just broken the compiler :/




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: