Hacker News new | past | comments | ask | show | jobs | submit login
Elements of Clojure (elementsofclojure.com)
199 points by ingve on March 17, 2016 | hide | past | favorite | 43 comments



The learning experience for Clojure is superb. Coming from C/Python languages, it really put things in perspective to learn to think more in terms of composing primitives than wrangling syntax.

OT: Does anyone know of other projects in the spirit of Pixie? To be more specific I'm looking for existing work on a clojure->rust project, would contribute or start one if none exist. Sorry for the offtopic!


I'm working on Clojure-looking lisp with a borrow checker and static analysis in the vein of Rust. It's especially tailored for games since most lisps don't account for that use case (GC, dynamic types). Here's a link, let me know if you have any questions: https://github.com/eriksvedang/Carp


This looks nice and feels right in terms of syntax. I'm not sure I see the value of exposing the borrow checker to the user however; couldn't it be used implicitly while handling the s-expr to c compilation? In any case I'd love to talk more about it, can I reach out via the github project?


Absolutely! We have a gitter chat that would be a perfect place for that kind of discussion https://gitter.im/eriksvedang/Carp


Just to be clear, you consider Pixie to be Clojure -> Python, and looking for something similar but s/Python/Rust, right?


Yes pretty much!

Speaking of python, there's also hylang: use python functions with lisp s-expr syntax. It's very cool


"Natural names allow every reader, novice or expert, to reason by analogy. Reasoning by analogy is a powerful tool, especially when our software models and interacts with the real world. Synthetic names defy analogies, and prevent novices from understanding even the basic intent behind your code. Use them sparingly."

I'm not sure that I completely agree, but this is a great conversation to be having!


Hi, I'm the author. I would be very happy to discuss this point (or any other) in more depth on the mailing list: https://groups.google.com/forum/#!forum/elements-of-clojure.


I really wish I had the time to learn Clojure. Between Rich Hickey's general wisdom and skill, my (small but fun) exposure to Lisps, and David Nolen's ClojureScript, it looks like a great environment to get into.


It's very much worthwhile. Apart from being a Lisp, which says a lot on its own, Clojure has a bunch of other "brain-expanding" features. Its approaches to immutability, concurrency, and higher-order function munging (reducers and transducers), in particular, are very enlightening even if you end up writing something else for living.


One's life changes so much, after they leave /r/javascript and join /r/clojure.

Forgot to mention, clojure community isn't doing too badly at StackOverflow:

http://stackoverflow.com/research/developer-survey-2016#tech...


I like Clojure a lot and I consider the community tops in intellectual firepower. That being said, there is nothing great about transducers, they are merely a necessary hack because of Clojure's immutable data structures. To get around this computation bottleneck, you mutate the code instead of the data.

So transducers are a great hack, but not a great feature of the language, more of a necessary evil.


"really wish I had the time to learn Clojure."

I would say don't bother. The language is a nice lisp (nice simplifications, great immutable ADTs, edn, pretty good async libs, etc), but the ecosystem tooling is still miserable IMHO - this is coming from someone who has worked around 6-12 months on a couple of clojure projects, so I'm not a die hard expert but I've worked enough with it to build with/have used clojurescript,clojure,java bindings,leiningen/boot,clojars etc.

I really wanted to like clojure. The idea that I could run my code on the JVM and in the browser is alluring. And, I would add, if you like using mvn or ant or gradle or in general the java way of doing things, you will probably think leiningen is good enough.

But I have zero tolerence for annoying/unnecessary headaches, and so I can't take it, and don't program any of my projects in it.

What do I consider good? Well an analogous project is Mix for Erlang/Elixir, and I think its an excellent example of what I wish leiningen were. Anyone can learn Mix in about 10 minutes, you can easily use it for erlang & elixir projects, I have had zero problems and stuff just works. This is not the case with leiningen, which makes building and deployments unnecessarily complicated in clojure.

I don't see clojure getting mainstream until someone starts concentrating more on the user experience in the tooling, though this may be more of a java/jvm originated problem, still it affects your whole experience while using clojure.


As much as I love Clojure and even more so ClojureScript, I would have to agree with you on this. To add to what you said, another big pain point for me is starting new projects. Although I wouldn't say don't spend any time learning the language, especially if you are new to functional languages, you can learn a lot from Clojure.


"Although I wouldn't say don't spend any time learning the language"

Yes, I can see your point. And there are some really good innovations clojure has made I think - specifically the immutable data types are really beyond compare, anyone wishing to learn more about immutable data structs should definately read their source in clojure its clear and understandable.

Still, if anyone wants to learn what lisp is all about. I'd just recommend common lisp (clisp or sbcl) for getting the special forms, repl, and beauty of macros bits. It also means you don't have to know anything about java to get the key concepts of a lisp, which is less overhead overall.

Regardless of common lisp or clojure, I'd always recommend anyone listen to Rich Hickey's talks on clojure, they are very good, especially his talks about values, composition, and simplicity. (http://www.infoq.com/author/Rich-Hickey)


I recently started my first Clojure/ClojureScript project using the Reagent template for Leiningen[1]. It gives you Ring/Compojure/Hiccup on the backend for simple routing and views, and Reagent with a client-side routing framework on the frontend. It took me 10 minutes to get it running and figure out what the different components were doing. I'm sure ultimately I'll roll my own setup but this is a good way to get going if you're new.

1: https://github.com/reagent-project/reagent-template


I generally see it as getting way better and the improvements over 3 years back are unreal. Figwheel with ClojureScript and Cider for plain ole'clojure make it pretty awesome developer experience. I just built a small project and finished it in half the time it would have taken in my normal tools.


Could you give an example where Mix is superior to Leiningen or Boot? Or where you've had problems doing something that in Mix was much easier?


the tooling is way superior to common LISP, since it can leverage the huge Java ecosystem. Clojure tooling - it's basically a jar, so if you know how to use Java, you can always fall back to that. ayes, if you want a fully integrated stack with Clojurescript it takes considerable time. I spent that time and it beats everything I know hands down. if you want to code like everyone else, use something else. if you want to 10-100x productivity gains long-term then spend the time. http://paulgraham.com/avg.html


Checkout this series and quicklisp and then with a straight face say tgat clojure experience is better http://malisper.me/2015/07/07/debugging-lisp-part-1-recompil...

I have used both professionally and find the dev tooling for clojure very immature. Debugging tools is not a modern feature yet clojure jas very little


http://endlessparentheses.com/cider-debug-a-visual-interacti...

The cider debugger is one of the best I've seen. I've been able to stop, inject code and create a test case for failing tests.

Cursive has similar functionality as well if you are afraid of emacs becoming a lifestyle :)


I completely disagree.

I don't have experience with Elixir/Erlang/Mix but I've heard great things about them.

One thing I'm sure though is that Leiningen and all its guides are second to none - read them https://github.com/technomancy/leiningen/blob/master/doc/TUT... .

Here's how your start a project with Leiningen:

lein new app

Here's how you build the whole project:

lein uberjar

Here's how you run your Clojure app:

java -jar my-clj-project.jar


Agreed. I've been doing Clojure (on and off) for about 4-5 years now and the tooling is as bad as it's ever been. Compared to Elixir/Mix it's downright embarrassing.

Incidentally, I've been putting more effort into Elixir/Erlang recently, I honestly see more of a future there (for me) than in Clojure.

Clojure was definitely a mind-expanding language for me though, no regrets learning it.


I love both languages, but I don't see the tooling in one as vastly superior to the other. Elixir certainly has Clojure completely licked in terms of compile times; also the Mix command-line workflow is just simpler (no messing around with project.clj vs profiles.clj vs ~/.lein/profiles.clj and god knows where else).

On the other hand, Clojure REPL workflow (although some assembly is required) is phenomenal. Clojurescript is a serious option on the client. And for runtime-level tools (profilers etc), you have the whole Java/JVM ecosystem, which is simply bigger, for better or worse, than the Erlang equivalent.

So for my money, it's definitely a two horse race.


i've been fairly happy with Leiningen but have no experience using mix - i'm curious to know what you really liked about mix that leinigen doesn't have.


    $ time lein --help
    ...
    lein --help  2,16s user 0,49s system 141% cpu 1,880 total

    $ time mix help
    ...
    mix help  0,51s user 0,51s system 129% cpu 0,789 total
This difference compounds and it gets rather annoying quite fast.


What are you doing the requires constant relaunches though?


You're definitely right, and from my experience the community has no interest in actually making the developer experience better. It's one of those "the first step to fixing the problem is admitting you have a problem" situations and for the most part the Clojure community can't even admit how bad their tooling is, they'd rather shift blame to the users and pretend that if you can't figure it out it means you're not smart enough to be using it.

I find Clojure is a lot more approachable if you start with Clojurescript / Reagent and go from there. You don't need to be running Clojure on the backend to make use of how awesome Clojurescript is, and it's about 10x more approachable.


"You're definitely right, and from my experience the community has no interest in actually making the developer experience better."

Where did you get this impression? The State of Clojure 2015 survey indicates that the Clojure developers are very interested in making the developer experience better, particularly around error messages, startup time and documentation.

I cannot recall seeing a single Clojure developer suggest that Clojure's tooling is good enough. However, it is a lot better than it was.


I've used Mix a bit and I didn't immediately see something that was so far ahead of Leiningen. Though I would say SBT is much closer in functionality.


Just give this a go: http://www.4clojure.com/

Don't need to install anything.


Clojure is great if you want to use it in an enterprise, multithreaded, Java shop working on large amounts of data. It's really great at exactly those things. If that's not your use case, then it may not be a very useful environment to get into.


I like Rich Hickey's talk about "complex" code. But believe it or not, it's possible to write simple (not complexed) code in other languages too!


Exciting! Zach Tellman's work had always impressed me. Looking forward to seeing what he brings this time around.


I wrote a similar kind of style guide for Python.

https://github.com/amontalenti/elements-of-python-style

"Elements of Python Style": goes beyond PEP8 to discuss what makes Python code feel great.

Looking forward to reading Elements of Clojure. Python is my primary language but Clojure is my hobby language.


Surely a good book. But it would be nice to see the complete TOC... Leanpub says it's 25% complete?!


The four chapters shown are the complete list, only the first chapter has been completed. I have tried to make this as clear as possible, let me know if you see a way to make it more so.


Can't wait, I'm sure I'll learn a lot from this.


I fear this reads not very precise, mostly arbitrary and random.

Examples:

> If a value is an arbitrary Clojure expression, it should be called form. If a macro takes many expressions, the variadic parameters should be called body.

A value is an expression? what is a Clojure 'expression'? Why should it be called a 'form'? If it is arbitrary, why not call it 'expression'? What does it mean to 'take an expression'? What is 'many'? A macro, the variadic parameterS, which should be called 'body'? The macro has more than one variadic parameter and they all should be called 'body'?

> These defaults derive from the idioms of the Clojure ecosystem and common sense.

'Common sense'? What?

> If a value can be anything, we should call it x. This is fairly rare, as usually the only things we can do with an object involve =, hash, and str.

What is a value? what is 'anything'? The next sentence talks about 'objects'. Is 'anything' always an object? Why don't we talk about 'any object', instead of 'anything'? What does the number of possible actions have to do how 'rare' a name is?

> Let’s consider a student datatype, which is represented as a map whose keys and values are well defined, either using documentation or a formal schema. Anything called student should have at least these entries, and sometimes only these entries.

A data type is 'represented' by a map? What does that even mean? 'Anything'? What is 'anything' in this context and what means 'entry' in this context?

This goes on and on.

> At runtime, our scope is any data we can see from within our thread.

What is this 'we' at runtime and what does it mean for 'we' to 'see' data?

> Conversely, we can think of sending an HTTP request as sailing across an ocean; any transfer of data, pushed or pulled, requires effort.

What?

> If a function crosses scope boundaries, there should be a verb in the name. If it pulls data, it should describe the datatype it returns.

What does it mean for a function to cross scope boundaries? What is a scope boundary?

> There are two kinds of macros: those which we understand syntactically, and those which we understand semantically.

There are two kinds of macros, those who begin with WITH and those who do not. What does it mean to 'understand syntactically'/'semantically'?

> To use with-open effectively, we must macroexpand it in our heads whenever it appears in the code.

what?

> syntactic macros

What is that exactly?

> Transforming arbitrary code is difficult and sometimes impossible

What?

Macros: ... > Readers must not only understand the semantics of the transformation, but also its exceptions and failure modes.

Personally I, as a reader, am more interested in the semantics of the generated code.

> macroexpanded syntax

How can I macroexpand syntax?

> Naming is a problem which cannot be solved, but also cannot be ignored.

What kind of problem is it and why can't it be solved. But then I can not ignore it? It can't be solved, but I can not ignore it?

Worse: much of the advice assumes a static text program where the only option I have is to read the text. Not a Lisp system with data<->code.

Probably also a good idea to write a glossary, where the basic terms (form, value, expression, scope, scope boundary, data, parameter, argument, variadic argument, syntactic macro, failure mode, ...) are defined/described.


Without a hint of irony: thank you for reading this so carefully. Some of the things you note are typos or poorly worded, most are things that I think are clear in context. In either case, yours is one of the very few comments about the actual book, rather than Clojure in general. I appreciate it.


> To use with-open effectively, we must macroexpand it in our heads whenever it appears in the code.

I'd like to add that if you find yourself needing to mentally expand a macro every time you use/read it, you have a poor abstraction. Consider going back to the drawing board and coming up with a good abstraction instead.


Right.

For a macro which only implements a 'simple' syntactic transformation, I would expect that I need to understand the semantics anyway. Additionally I need to understand the syntax it implements.

What I don't want at all, is to macroexpand macro forms. If I need to macroexpand those, then I would do this for debugging, when there is something wrong during expansion or with the generated code. I would also do the expansion not in my head, but using a function called 'macroexpand'.

For example as a reader, I don't want to macroexpand the following form in my head:

    (with-open-file (foo "bar.text")
      (read foo))
What I really want is to understand the syntax of WITH-OPEN-FILE and semantics of such a form. To what it transforms itself is the least important thing I (as a code reader) want to know when dealing with macro forms.

* it's a WITH- type macro which sets up a scope.

* a WITH- type macro usually expects a variable, something which helps creating an object that is bound to the variable and a bunch of options

   (foo-var "the-file-name.text" :direction :input)
* it then expects a body, a sequence of forms, where the variable FOO-VAR is bound to a file

That's the syntax.

The semantics is that at runtime it opens a file as a stream and makes sure that on all forms of exit, the stream gets closed.

To what form this actually expands (even if it is a simple transformation) is uninteresting and may be different from implementation to implementation.


Really brillant !




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: