Hacker News new | past | comments | ask | show | jobs | submit login
Fourteen Months with Clojure (skyliner.io)
300 points by lackbeard on March 31, 2017 | hide | past | favorite | 187 comments



I only worked with Clojure professionally for a couple of months, but the experience was less than stellar. Most of it probably had to do with the codebase being written by people without any experience, but a couple of problems could be traced back to the language itself.

I'm preparing to write a lengthy post comparing Clojure and Racket, the other Lisp I have been using for personal projects for a couple of years now. My opinion is that Racket is a better language overall, although it may be less practical in terms of writing production code and it certainly lacks some of the nice features Clojure brings. On the other hand, many of these nice features are available as libraries (not only in Racket - in most Lisps, including Elisp and Common Lisp).

For the last two years, I used StumpWM as my window manager and so I learned some Common Lisp. Again, as a language, I think Common Lisp is still better than Clojure, although it's definitely stranger.

I use Emacs as my default "computing environment", so I naturally learned Elisp, too. This is the only Lisp I used which I'd consider inferior to Clojure, but even then it works quite well for its use-case.

All in all, from the perspective of Lisp family of languages, Clojure doesn't seem to be exceptional in any aspect. It does have nice features, and I bet it feels much better compared to Java, but it also has some downsides to it which are irritating when coming from Racket or Common Lisp.


This experience is extremely common. I have lost track of the number of people who have formed a negative opinion of Clojure because they were forced to pick up the pieces of a half-baked project written by someone who wasn't familiar with Clojure or its idioms.

It should go without saying that this should not actually reflect poorly on Clojure as a language.


The problem is that problem clojure code is the gift that keeps on giving, and it boils down to the problem I have with clojure in general:

Bad clojure code is unmaintainable spaghetti code; which gets worse over time, as people attempt to 'patch on' fixes without doing the heavy lifting of trying to figure out:

- What was the original author actually trying to do?

- Why the heck did they do it like this?

- How do we create the same functionality and prove it works with these rubbish tests that only test the individual units of work, not the application function?

- Why is it all in one giant file?

I've never seen code bases descend into chaos as fast as our clojure ones have.

Nice, elegant clojure is a pleasure to work with for personal projects, but I'm never using it professionally again.

You might argue it doesn't reflect on the language, but I think it does. Given what I've seen, I'd argue that clojure has an inherent complexity that results in poor code quality outcomes during the software maintenance cycle.

...specifically, sections of bad code have a disproportionately negative effect (compared to other languages) on the surrounding code and negatively impact the entire project's code quality.

You hack something out for a deadline? You better go back and clean it up, because if you don't that codebase is screwed. shrug That's just been my experience over the last year on three different code bases.


This has been my experience as well. It allows you to write extremely dense code, without type checks to help you out. And it imposes a functional paradigm, again without type checks to help you out. And it makes new developers think in ways they're not used to, again without type checks to help them out. So the combination of these makes certain that some way or other, teams will write contorted code that is in major need of refactoring ... without type checks to help them out.

I switched to F# and "get" FP much better now. I'd know what I was doing if I ever switched back to Clojure. But I have no good reason to personally, and all the aforementioned reasons not to professionally.


No language I've used is exempt from the possibility of people writing bad code evoking those questions.

In fact I've had these reactions at various times to code in every language I've worked in (including Clojure, to be sure), but I don't agree with your argument about Clojure having greater than average inherent complexity.

Once you get comfortable with parens, the core seq functions, and even a basic understanding of laziness & immutability, so much of the inherent complexity you find in many programs just goes away. Well -- most of the time. Again, Clojure is not going to stop you from negating these niceties with bad decisions.

The one thing I'd maybe admit to it being a little above average on is the temptation to over-engineer due to novelty or feeling clever. Let's rub some core.async on it! Parallelize all the things! It's actually easier to do this because of the lack of inherent complexity in Clojure -- as the author mentions, everything is oriented around simple maps & lists, so juggling them and squeezing them through Rube Goldberg machines of transformations and mystery macros is definitely a thing you can do.

But, ideally, you just learn to . . . not do that. Like abusing Ruby metaprogramming.


Only in good clojure code.

Tell me you've never picked up a piece of clojure code and gone, WTF does this do? What are these global channels for? Why is the whole application constantly updating a top level full-application-state atom? Why are we blocking indefinitely with some magical invisible state hidden in a closure as we iterate over a collection of objects to process?

If you have a beautiful clojure code base, it's fantastic...

    But, ideally, you just learn to . . . not do that
How do you fix a bad code base when the functions aren't pure (global injections, global channels), or are 'pure' only in the sense the input is the entire application state, including non-pure objects like a database handle? Or your functions are non-deterministic due to some kind producer/consumer race condition?

I get it; don't do that.

...but what do you do when it's too late, and someone has already made those bad decisions?

Where are the debugging tools to help you figure out what's going on, and the refactoring tools to help isolate code units and replace them?

I mean, sure, you could argue that's an issue in any language, but all I can say is that I've used a lot of other languages, and the only other similar experience I've had was working with perl.


> What are these global channels for? Why is the whole application constantly updating a top level full-application-state atom? Why are we blocking indefinitely with some magical invisible state hidden in a closure as we iterate over a collection of objects to process?

I think something that would help the Clojure community improve, especially with regard to introducing Clojure to a team is to ask why this kind of thing doesn't happen in Python.

I know Python doesn't come with atoms, but you could certainly stick the whole application state in a global variable. You wouldn't though if you're a working programmer with a modicum of experience on your first Python project. Do people just see some new idioms and forget everything they already knew about programming?

Python does have closures, and you certainly could write one that blocks due to some hidden state or does something stupid involving multithreading. This isn't seen as a problem with the language though; it's seen as a sign that the person who did it might need to work more closely with someone more experienced or that the team needs better code reviews.


> You wouldn't... stick the whole app state in a global variable.

Huh. I thought this is what Om Next did to great success.

* https://medium.com/adstage-engineering/realtime-apps-with-om...

[edit] i misunderstood your argument. Your not implying a single state is bad, your saying their is nothing in python to deal with it.


I suppose. When you update and depend on a global atom your functions aren't pure, at all, in any sense.

Why are you even using FP at that point?

Global application state should be represented in a single root storage; the application data store (ie. database), and the interactions with it should be controlled and sanitised.

If you have a thousand little places across your code base updating and reading from the database, that's horrible code too, in java or in clojure...

Modern UI frameworks are carefully controlled access and update to the display state for a UI; they happen in a controlled and orderly manner specifically to prevent the chaos you get otherwise; if not, you're doing it wrong.

This section in the om docs covers their solution, which is quite elegant: https://github.com/omcljs/om/wiki/Quick-Start-(om.next)#glob...

(notice, you don't just reach out and update atoms directly by hand; there's nothing wrong with having a global application state; that's a good thing, but directly interacting with it is not)


You were closer to it the first time: it's usually a mistake to stick your whole application state in something resembling a god object. The client-side of a single-page application might be an exception under some circumstances when the language provides sane tools for dealing with it.


> No language I've used is exempt from the possibility of people writing bad code evoking those questions.

Of course there's no stopping a determined person from writing bad code, but there's a big difference between various languages in how easy it is to accidentally or unknowingly writing bad code.


It may be that you've been unlucky. I've been using Clojure for nine years now, and the majority of codebases I've worked on have been very clean. My experience is that Clojure tends to produce flat codebases, whereas OOP languages produce codebases that tend to be deeply nested.

I can't prove any of this; it's all anecdotal, but I thought I'd mention that your experience might not be typical.


I've only played with Clojure a little bit. Can you explain what makes bad Clojure code especially bad? Or why the badness leaks into the surrounding code?

(The impression I've gotten is that the persistent data structures are very nice, and the language has some good ideas to go with them, but overall it didn't entice me away from Scheme or Common Lisp. But that impression doesn't explain why you had this bad experience with troublesome Clojure code.)


I've been working with Clojure professionally for the past 6 years and I've had a very positive experience. My team finds that code is actually very maintainable.

Two main reasons for this are that Clojure is immutable by default, and it has minimal syntax. Immutability allows projects to be naturally compartmentalized. You can do safely do local reasoning on parts of the project. Meanwhile, simple and consistent syntax means that it's easier to read and understand the code written by others. There are far less language quirks to remember than in most languages.

I also find that the editor integration with the REPL is a huge plus as well. Whenever I run into code that I'm not sure about, I can just run it and see what it does.


>Bad clojure code is unmaintainable spaghetti code; which gets worse over time, as people attempt to 'patch on' fixes without doing the heavy lifting of trying to figure out:

Couldn't you say the same thing about most languages?


That would be my take on this too. If you get code written by people who don't know the language, the result is pretty much always dreadful. I know of no language that mitigates this.

However, I'd be interested in which parts of Racket and CL klibertp thinks are making them better languages than clojure.


This is why as much as I dislike Go, I'm glad to use it on a team


"I've never seen code bases descend into chaos as fast as our clojure ones have."

Is this a function of the teams or the language?


Possibly, but there's nothing particularly wrong the other code they maintain in our case.


> this should not actually reflect poorly on Clojure as a language.

Why not? If a language's goal is to be practical (as is Clojure's), then it should take practical concerns into consideration. This is something that the Java community definitely gets right.


>Why not?

Because someone who has never heard of map/reduce/filter/partial will need to do some studying to leverage the spine of the language in order to make living, breathing code-organisms.


God forbid someone would have to learn how to use map, reduce and filter - 3 functions present in Ruby, Python, Javascript, PHP, Erlang, Scala and many others.


Incredibly useful functions! Although, not common knowledge.


I have a feeling this issue can be made more abstract and cover more languages, more paradigms than just that of Clojure. The real problem here does not seem to be Clojure, but finding developers as well as faith in new (or small) languages to build that solid, big project that has stood the test of time before you abandon it for something you have an easier time finding developers for, or faith in.


My team does code reviews and pair programming, especially when onboarding new devs. This helps new hires get comfortable with the style the team uses, and ensures we have clean code in our project.

We've hired a number of devs for our Clojure projects, and none of them knew Clojure when they started. We found that this process has worked very well for us.


Strange how this is the top comment but it contains no substantive facts.


Yeah, I'm surprised myself... FWIW I wrote it that way on purpose: I don't have the time right now to cover the specifics, and mentioning some things without proper explanation - in the context of PL advocacy - would almost surely lead to a flame war.


No judgement on your comment. It was a perfectly fine comment. I was just confused as to how other comments with objective examples were not higher up.


I've "shipped" a few racket projects in "production" now, but have never used clojure - only read about it, including posts like the above. I've had more than a few people tell me about how amazing clojure is as a language but never from people with experience in other lips. I'd love to read about your experience.


I'm surprised by my preference for old, cryptic, stranger languages. car cdr are better symbolic representation in my mind than first and rest are in clojure. Especially when trying to think recursively.. There's a mystique in languages.


Car and cdr were register names. First and last are logical. Reminds me of Alan Kay complaining how Smalltalk and Lisp eat their children.


The typical recommendation is to use CAR and CDR when working with conses and using FIRST and REST when working with proper lists.


The nice thing with CAR and CDR is that you can have combinations like CADR, which is (CAR (CDR A-LIST)). Many structures are rarely linear (say a s-exp repr of a JSON object), and the C*R family of macroes are really useful.


"CADR" should be properly called "second" instead of this dense mnemonic you have to decipher every time you see it.


Lisp usually provides functions like first, second, third, ... CADR would be found in old code or code which works on the cons-level.


Not all C*R functions are linear list access. I guess both those and FIRST, SECOND, etc. have uses. There's no need for ideological fights about them.


What do you suggest for CDAR then? Or CAAR?


> What do you suggest for CDAR then? Or CAAR?

A user-defined function with a name indicating what you're reaching for. Occasionally you might want to write a generic cons handling function where all you can say is that it's the car of the car, but those instances are extremely rare. The only place you should be writing car and cdr compositions is in one line wrapper definitions, and there the benefits (and drawbacks; it doesn't really matter which you use) of the c*r composition functions don't really show themselves.

edit: It's the same logic as using first and rest when you're dealing with a list (that is represented with conses). If you're handling parsed JSON, why would you be talking about cars and cdrs?


And where did I say to use them everywhere? I usually use them in let bindings decomposing complex data structures:

  (let* ((data '((user1
                  (email . ("bob@dom1" "bob@gmail" "hey@bob.com"))
                  (sshkeys . ((default . key1)
                              (alt1 . key2) (alt2 . key3))))))
         (user (cdr (assoc 'user1 data)))
         (email (cadr (assoc 'email user)))
         (sshkey (cdadr (assoc 'sshkeys user))))
    (list email sshkey))
The thing is one would be (rest (first (rest (assoc ..., the other is (cdadr (assoc ..., which are equivalent in effect (and the latter has way less parens). I dont understand why there's an ideological you should use this or you should use that thing going on. C*R stuff is an abstaction over combinations of FIRST/CAR and REST/CDR, and I dont see why they should be avoided at all. They've got their place, and all this thread is full of arguments backed by mere taste.


I know.


I'd like to read your post when you publish it.


Send me an email (address in my profile), I'll ping you once it's ready.


Any chance of talking you into adding a feed (RSS, Atom, or h-feed markup on the existing html pages) to your blog?


Ok, so I managed to add RSS-like thing to my blog. The format works with my RSS reader (some simple Chrome extension), but let me know if it doesn't work elsewhere. I even have a post about it: https://klibert.pl/posts/rewriting_the_blog_again.html ;)


Yes, works! (article content in the feed would be nice, but as long as headlines are there and the urls are correct it's ok in my book ;)


Definitely possible - I'll see what I can do over the weekend.


I really enjoy Clojure. Persistent data structures, core.async, and transducers have all been really pleasant abstractions to work with. I've been writing in it for fun and utility for maybe five years now, and have used it for one mid-scale contracting project, so I hope that that's sufficient background to prevent dismissal of the following as the gripes of a beginner, but: it's so strange to me, for all of the appeals that Clojure makes to an interactive development process involving the REPL and immediate feedback, that a first-class debugging experience on the level of Common Lisp's doesn't seem to be on the roadmap at all. The type of explorative developing you do in that environment, with the ability to drop into the debugger on error, alter the function body and restart the frame with your new code, seems about as far beyond the standard Clojure workflow as REPL-based development is beyond the save-compile-fix cycle of less interactive language ecosystems.

I can deal with the Java stacktraces and the occasional mystery of which concrete class is actually backing IPersistentMap after however many invocations of assoc, but if the experience of writing code is going to be a pillar of the language's sales pitch, why isn't this sort of next-level debugging experience on anyone's radar? The CIDER debugger is nice, but it's not CL nice.


There's a tracing debugger for CIDER (http://bpiel.github.io/sayid/) and Cursive plugin for InteliJ integrates a step debugger (https://www.youtube.com/watch?v=ql77RwhcCK0), but let's agree matching CL's debugger is a tall-order - it's extremely nice and only deals w/ CL, while Clojure, being a hosted language, cannot hide the underlying implementation (interop calls are so common and a big selling point of Clojure in the first place).


You're right, and I ought to have mentioned the difficulty of implementing something like that for Clojure. So maybe the issue is more holistic, which is that it's hard to tell how it's positioned: it's not quite the sort of language that drills as deep as possible into the affordances of being a Lisp, but it's also not quite the sort of language that does everything just well enough, given its investment in, for example, immutable data structures. That doesn't stop me from using it, or presumably anyone else who gets value out of using it, but I sort of idly wonder what the cases are that it's uniquely suited to.

It might not matter: maybe being the obvious right choice for some use case in the abstract isn't that important, and contextual factors re who's doing the work and how they think are just as relevant to choice of language. Still, it seems like we tend to talk about languages now in terms of their most indispensable applications, for better or for worse.


You could say elixir (via erlang) has this kind of debugging flow. Although not automatic, you can have your program run inside a repl and inspect anything you like. There is also an "observer" program that shows you the running system. All the erlang processes, how they are connected, Who's talking to who, memory use, etc...


Clojure is interesting in a lot of ways. I've toyed with it a lot, but what actually keeps me from using it much is how tightly coupled it is with the JVM. I know this is its big selling point for a lot of people but for me personally I don't enjoy having to know Java's APIs and ecosystems to get things done.

Is anyone else out there like me who wishes there was a standalone clojure implementation that wasn't a hosted language? Add a modern package manager on top of that like cargo or mix and I would love to write new projects in it, because clojure itself is a very pleasant experience.


Since Clojure is advertised a lot whenever there's a Common Lisp story, let me do the reverse: give Common Lisp a look.

It's a member of the Lisp family that:

- has a lot of high quality, implementations on a lot of different platforms be they native or hosted,

- is multi-paradigm so it doesn't lock you into just functional programming, or just object-oriented, etc.,

- can go from high-level code to fast compiled code (coming close to C++ speeds, like twice as slow.. not many high-level languages manage this),

- has been an ANSI standard for a long time and because of that:

- is a stable language: code from decades ago can be run without much (or no) porting. A while ago code from the early 1970's (or was it the 1960's) was made to run relatively easily, although I cannot find the link,

- is still a joy to program in, it brings together features that have been popping up in other languages but only in CL they feel so seamless,

- has a really nice development environment in Emacs + Slime (if you can stomach Emacs),

- has IMHO a nice and helpful community,

- has a great history, you'll come across almost any aspect of software & computer history when diving into CL.

The CL community has always been hotly debated and is usually negatively depicted on HN. I've never found it to be too bad, although it has attracted it fair share of trolls in the past.


In the spirit of highlighting other, non-Clojure, lisps to look at, I'll mention Racket.

Compared to Clojure:

- Not hosted (on JVM)

- Similar disposition on nudging you toward functional programming

- Less syntactic sugar (some of Clojure's sugar I find nice, but is divisive for some)

- Older

Compared to CL:

- One canonical implementation. This is good (consistency) and bad (choice, variety)

- More biased toward functional programming than CL

- People seem to think Racket's macros are more powerful than CL's, but I'm no macro-master

- Package management (raco) feels much more pleasant, subjectively, to me.

- Develop in Emacs or DrRacket; the latter being a pretty decent IDE for newcomers and others who'd haven't seen the true light of Emacs.

- Newer, feels newer

I started carving off small chunks of time a few years ago to try and hear the music of the spheres everyone talks about with lisps. I started with CL, then tried Clojure, and have mainly been dabbling in Racket these days. Subjectively, Racket has been the best beginner experience by far. As for hearing the music of the spheres, I'm still waiting.

EDIT: I forgot Rackjure! Racket is also a framework for building new languages (languages that are compatible with Racket, generally). Rackjure adds some of the Clojure-isms to Racket and seems to be used by a number of packages

Also, Typed Racket is another such variant of Racket that uses types for safety and performance reasons.


which CL implementation is considered the most active/modern? and which one do you use?


I'd say SBCL and CCL, but CL implementations are not like Schemes where there are huge incompatibilites and practically you have to target a certain compiler. With CL the compiler-specific stuff are very-rare and small in number, so most of the time you should be fine on any compiler with ongoing development.


SBCL and SBCL.

http://www.sbcl.org/


Agree SBCL and SBCL if you're looking to do linux for scientific or web. (Let's get real, is there anything else?)

Clozure Common Lisp (http://ccl.clozure.com/) is excellent if you're on the mac.

Windows I'd do Lispworks or Allegro (both commercial lisps), although rumor has it SBCL for windows has gotten much better. Admittedly my last experience doing anything on windows was probably ~5-6 years ago.

Great thing is, the language is standard, so generally you can just pick one and start messing around.


lispworks actually looks very good to build in house Line Of Business applications

but the cost and price, really stand in the way of it being used in a greenfield/side project, where you work on something new without necessarily having your management approval, and then present it to them once .. it works


I'm one of those who think the JVM is a big plus instead of a big minus. For example, here is a post how you can easily deep dive into the internals of the JVM to figure out a problem: https://8thlight.com/blog/colin-jones/2017/03/17/everybody-o...

I wanna see this kind of tooling for other languages. The JVM gives you amazing tools that are already there for you even if you're not an expert.


In a typical Lisp system you measure runtime and you'll get GC time, too. In Common Lisp you use the TIME macro for that.

In LispWorks I would for example set a memory check for a certain memory size. That would call a function when memory grows over a limit.

You then don't need to look at a heap dump, you can inspect the Lisp memory from the inside with the usual tools...


Clojure is interesting in a lot of ways. I've toyed with it a lot, but what actually keeps me from using it much is how tightly coupled it is with the JVM.

Clojure the language's coupling with the JVM is actually rather loose, precisely because it's hosted. However, Clojure the ecosystem is definitely coupled. However, I think this only rears its head when you need to incorporate a library and deploy. Once you set up your CI environment, I don't see this as being that big a deal that often.

Add a modern package manager on top of that like cargo or mix and I would love to write new projects in it, because clojure itself is a very pleasant experience.

What of Leiningen?


Leiningen is a pain in the butt compared to cargo or mix, and it's verbosity and package setup can be truly painful at times.

On the other hand, boot[0] is simpler, can do everything Leiningen can do.

Leiningen kept me from being effective with Clojure. Boot got out of the way.

[0] http://boot-clj.com/


There are elements of the Clojure design philosophy that are likely to keep it in the realm of "hosted language" for some time to come. That said, with the advent of self-hosted ClojureScript, there is no reason that you need be tied to the JVM just to use Clojure. In some regards, I see the ClojureScript community growing at an even faster clip than the parent language (though Clojure does have a large head-start).


> what actually keeps me from using it much is how tightly coupled it is with the JVM

I'm curious: where do you actually see that coupling?

I find that I barely notice the JVM in practice. There is the main function, sure, but otherwise I rarely use the Java APIs myself. What I do notice is the fantastic runtime with state-of-the-art garbage collection, something I've learned to appreciated after spending years with various Common Lisp implementations.


> I find that I barely notice the JVM in practice.

slow startup time, difficult dumping/loading images, difficult AOT native compilation, overly complex loading of code (-> custom class loaders), no TCO, no late binding for functions, host numerics shines through, delegation of functionality to the host environment using host language constructs, no resumeable condition system, jvm stack traces, error messages displaying JVM details, ...


It's pretty noticeable in the stack traces.

I'm curious how you avoid the Java APIs since Clojure uses uses parts of the Java standard library for things like strings and dates rather than implementing a Clojure wrapper. Are there good Clojure libraries to use? It's been a while since I looked.


It's true that the stack traces are horrible.

As for the APIs, I almost never use anything from Java directly (well, maybe except for Thread/sleep?). I use libraries like clj-time. Mind you, I'm not actively going out of my way to avoid the Java APIs, it's just that I don't encounter them that often in practice.


Not too hard to pull in abstraction layers: https://github.com/clj-time/clj-time


clojure.string also helps


Regarding coupling to the JVM: https://funcool.github.io/clojurescript-unraveled/

Regarding being a hosted language... that was it's intent. Use one of the best run-times available, with a better language. Granted once you design the language to be hosted, it can find other hosts as we see in ClojureScript.


With ClojureScript you end up relying on Google Closure Compiler as much as you rely on Java with plain Clojure.

It's true that being a hosted language is one of the goals of Clojure, but it's also true that being a hosted language is a non-feature (at best) for some people, including GP and me :)


How is Clojure 'designed' to be hosted? What is the design there, other than just leaving out functionality and delegating it to the host environment?

Other languages which weren't 'designed to be hosted' can't be hosted? Hmm, I was under the impression that the JVM hosts a lot of standard languages, for example Scheme dialects and Common Lisp.


It makes access to the underlying host a first-class language feature, so interop with the JVM or JavaScript is super-simple.


It's really not though. ClojureScript runs on Node.js just fine, and you have stuff like Lumo https://github.com/anmonteiro/lumo for scripting, and even a full blown framework for web development https://macchiato-framework.github.io/

The main advantage of keeping Clojure hosted is in being able to leverage the host ecosystem. It's simply not practical to implement an ecosystem comparable to the JVM or Node from scratch, especially for a niche language.


Why not just use one of the Common Lisp flavors then? Racket has been rising in popularity last few years: https://racket-lang.org


mainly because the CLs aren't clojure i.e. AFAIK don't use persistent data structures, and don't focus on robust concurrency primitives, don't have STM, etc...

But if there is a CL out there that does all that I'd give it a try tonight :)


You could try LFE[1] instead if what you want is concurrency, but it still is a hosted language, the only difference being that it relies on Erlang instead of Java ecosystem.

[1] http://docs.lfe.io/current/index.html


Lisp (what is a Common Lisp "flavor"? Racket is Scheme) has answers to all... Of course the benefit of Clojure baking those things in is you can rely on them in other people's programs, but still idiomatic Lisp seems to do an ok job managing state, especially compared to other languages. Plus its other features that Clojure lacks might make the tradeoff worth it.

Parallel programming: https://lparallel.org/overview/ Functional collections: https://github.com/slburson/fset STM: https://common-lisp.net/project/cl-stm/doc/index.html


Racket is not Scheme (anymore). Racket is a Lisp-1 with lots of different features and "batteries included" stdlib. It's a "descendant of Scheme".

Worth taking a look into definitely, but it doesn't have STM and "persistent data structures" which jfaucett seems to want. It does have solid concurrency and parallelism primitives, though.


Just to be that guy: Racktet is a Lisp (well: Scheme) flavor but not a Common Lisp flavor. (Common Lisp is also a Lisp flavor like Racket.)


Also there is an object system called Flavors for the early Lisp flavor ZetaLisp.


Racket is not a common lisp flavor.


I sorely miss writing Clojure - it's the one thing I had to give up on my current role (largely due to lack of time, but also because I mostly write stuff in Python as a sort of lingua franca), and I find most of the idioms slipping away though disuse.

This was an amazing read in the sense that a) it brought back all the reasons why I began using it in the first place.

b) it had some great lines - both the "when the going gets tough the tough use maps" title and this lovely tidbit:

"...maybe Scala has answers to all of these problems now, as I haven’t had the pleasure of using it in several versions. Do not @ me to talk about this."


I write lots of Clojure and ClojureScript everyday, and the article really resonated with me. Especially the parts about not using anything too complex or fancy, and sticking to simple tools. My code also has very few macros, and relies on lots of (complex) maps, using clojure.spec to keep them in check.

I think you get even more benefits from Clojure if you write apps that run both server-side and client-side (in the browser, using ClojureScript).

Much thanks to the author for nicely showing a good use case for the Either monad. In general, I think there are lots of great concepts in category theory, but well hidden behind horrendous naming (bind/return, anyone?) and lack of good practical examples (well let's fmap inc over a vector here).

And since it seems everybody pitches in something about some language being inferior or superior to Clojure, I'll contribute something, too: I would not have been able to write PartsBox.io (https://partsbox.io/) without Clojure and especially ClojureScript. It boils down to practical reasons: code size, abstractions that I can build on, avoiding accidental complexity.

I don't like participating in discussions that compare programming languages these days. I feel these are often very shallow. As an example, I used to program a lot in Common Lisp, and I think it can't meaningfully be compared to Clojure/ClojureScript. You don't see that until you've written several large multithreaded applications with zero deadlocks (thanks to STM), after you've used core.async to simplify complex and bug-prone code, after you've used transducer pipelines to parallelize a large stream computation completely avoiding the horrors of Hadoop, or after you've sent your data structures over a websocket to code that is actually the same code that runs on the server (cljc, files that compile to both Clojure and ClojureScript). Seriously — how can I discuss the finer points of syntax (oh, the parentheses!) when I am able to ship React-based apps that do isomorphic rendering (the server pre-renders a page, and JavaScript plugs into it later), all in Clojure+ClojureScript, without even touching node.js? You start to appreciate those things only when you write large applications and your time is limited. Going back to my Common Lisp background, I also wrote web apps in CL, and there is no way I would even think about going back.

There are things I do not like about Clojure, but so far I haven't found anything even remotely comparable in terms of real-world productivity. So I'm sticking with what works really well, at least until something better comes along.


Can't upvote this enough.

> There are things I do not like about Clojure, but so far I > haven't found anything even remotely comparable in terms > of real-world productivity. So I'm sticking with what > works really well, at least until something better comes > along.

This is exactly my take on it. I find myself thinking about Clojure adoption more and more these days, and where is the disconnect in all this.

I wonder if the idea of clojure both scares away "normal" programmers who are really just focused on using languages in their professional lives and want a simple, focused productive experience, and instead bring in more academic/experimental/recreational programmers who then actually dislike the fact it's made choices to promote use in the real-world.

Super fascinating stuff!


This should be the top comment in this thread. A lot of garbage comments overall.


I want to echo comments here by sharing that my professional experience with Clojure has been dismal. It's definitely unfortunate because Clojure itself is a wonderful language.

To give you a better idea: no use of component or similar, no ability to run the app 100% in a repl. A junior engineer prematurely split a monolith out into microservices that weren't truly isolated in their function (they shared the same underlying db and a handful of "libraries" that were not libraries) -- a true fucking productivity nightmare. Shipping a small fix would oftentimes involve version bumping and deploying 3 separate jar's before you could even QA it. You NEED to use your repl and reloading tools when doing Clojure. You can't rely on your test suite, jenkins, or suffer through JVM restarts every few minutes. When people do not follow these concepts Clojure becomes an absolute fucking nightmare to work with.

There is a stark difference between programming in a functional/immutable style and using a functional language. You can write imperative and procedural style programs in Clojure just as easily as you can write functional programs in Python.

How can we fix the Clojure ecosystem? It will be hard. Lisp is oftentimes an esoteric environment. You don't see a lot of Lisp/Clojure (correct me if I am wrong) in environments where you need to be pragmatic and move quickly. There also isn't a "rails" for Clojure. With the exception of Lein (which is a truly remarkable contribution to the open source community) -- there isn't a whole lot to use as a metal model or better a popular style of building and organizing your apps. It's all the rope to hang yourself with -- and if you aren't an experienced hacker you're gonna hang yourself.

Finally: I find it really tough to find solid use cases for Clojure as opposed to something else. The real things it has going for it are java interop and easy concurrency. I usually have a very hard time choosing Clojure over Python because of how quickly I (personally) can use Python to build extremely readable, modular, single-responsibility, functional, etc... code.


Let me throw in a counter. I've been working with Clojure for over seven years, many of those on what is possibly one the largest Clojure code bases around (~150k loc, given clojure's conciseness, that's a lot). I find working with Clojure professionally to be a joy. Our team heavily relies on our test suite and jenkins in addition to the repl.

Using the test-refresh plugin there is no jvm reload time between test runs. You edit code and almost before you look at the terminal affected tests are rerun. I'm not sure why a small fix would require so many version bumps, but that's not a clojure specific program. If you are doing good version control and modify a library somewhere deep in the dependency chain you will have to bump version numbers up the line no matter what. As for jars, again, I'm not sure why you have three unless you are running a big distributed system, in which case that seems inevitable. Uberjars package your entire program into one easy to deploy jar. Which brings me to another major strength of jars, deployment is amazingly easy. All you have is java and any jni libs as production dependencies. Updating requires modifying only one or a few jars. To me, that's a major selling point, not a negative.

Meanwhile, deploying python to production is incredibly hard. Getting just the right set of library versions is a major pain. Do you use the system built in packages? Or perhaps you have to use pip, or another manager? Anaconda?

In my opinion clojure is absolutely great for environments where you need to be pragmatic and move quickly. Being on the JVM and having great interop really contributes to that too. Being able to use the huge java ecosystem in addition to the clojure native ecosystem has meant massive productivity gains for us.

It's surprising that others experiences are so different. Perhaps it's a lack of "best practice" documentation for clojure?


My team is responsible for building small services on top of existing databases (or other API) and we found real success with Clojure. It took a little while to learn, but after you understood the basics it was damn simple to dig into a deeply nested JSON response to get at only the data you needed.

We have since moved away from it because of the inertia required to get past the curly fingernail stigma. But even with Go/Python we haven't been able to get the same velocity compared to Clojure services.


This is the perfect use case for F# REPL and type providers. Take a little time to learn it, and it's like code starts writing itself.


I'd love to see some concrete examples of what you're referring to.

Also, I can understand the team apprehension. I don't think lisp syntax is so bad as compared to the general amount of inertia required to do clojure well: emacs or similar, proper reloading, componentized services, etc...


"curly fingernail stigma"

huh?


Bad translation of parentheses, perhaps?


That's a pretty good guess. Makes sense.


> you don't see a lot of Lisp/Clojure (correct me if I am wrong) in environments where you need to be pragmatic and move quickly.

I don't know if you're wrong but isn't that the opposite of what PG claimed in his famous "Beating the Averages" essay? http://paulgraham.com/avg.html


I loved the either-monad solution. The problem is something I've experienced many times in my clojure adventures, usually resulting in either dirtying the function in question with its own, case specific logging or creating the if-let monstrosity you demonstrated.

As a side note: there's something very comforting when you immediately empathize so strongly with a programming problem someone is writing about


Oof, I don't know. I love Lisps, generally, having gotten my start with Scheme back in the day. But that monadic thing just rubs me the wrong way. I don't program Clojure, though, so maybe it's just lack of experience. If they had gone with Rust's Ok/Error rather than Haskell's Left/Right, maybe it would be better to me. (Yes, yes, I know, the monad is more general than that, but if it's used 99% of the time for handling errors, maybe optimize for that use case.)

I program in Elixir now, which I'm totally loving. It's also a "no early return" and "totally immutable" language, so I wonder if any "cross pollination" between the languages is possible. This is how I would code the example given in Elixir:

    with {:ok, x} <- foo(),
         y when not is_nil(y) <- bar(x),
         {:ok, z} <- goo(x, y) do
      qux(x, y, z)
      IO.puts "it worked"
      true
    else
      {:error, :not_fooable} -> IO.puts("foo failed"); false
      {:error, :no_bar} -> IO.puts("bar failed"); false
      {:error, :no_goo} -> IO.puts("goo failed"); false
      nil -> IO.puts("something (bar?) returned nil"); false
      _ -> IO.puts("some other error"); false
    end
Elixir's cool "with" / "else" macro is used for chains of calls where the intermediate steps can fail. In fact, it was introduced not too long ago, just to deal with nesting code issues like those raised here!

It relies on elixir/erlang's pattern matching to specify the happy path, extracting out the success responses (e.g. {:ok, x} will match a response of {:ok, 5}, and x will hold the value 5). If any of the happy matches fail, it falls through to the `else` and you can catch any particular failures you want there.

It's a really great system. Someone should try to add it to Clojure! Not sure how well the "pattern matching" aspect of it can work, though.


That part really resonated with me as well. Well the whole article did... it really matches my professional experience with Clojure (except I've never looked at transducers.)

I still don't feel great about error handling in Clojure in general. I can't find something that feels both practical and idiomatic. I want to use monads but it always feels like huge impedance mismatch with the rest of the language (most of the time due to nil being semantically overloaded.)


It looks like this in my personal Lisp dialect, with no new macros:

    (match (foo)
      (#no (log "foo failed") #no)
      (x (match (bar x)
           (#no (log "bar failed") #no)
           (y (match (goo x y)
                (#no (log "goo failed") #no)
                (z (qux x y z)
                   (log "it worked")
                   #yes))))))
I imagine any language with a match macro, like Racket, could look similar. Using monads or exceptions ought to look nicer, but I'd kind of rather not pull out the big guns.


For the nested if-let mess, I'd probably do something like this:

    (let-every [x (foo)     err "foo failed"
                y (bar x)   err (format "bar %s failed" x)
                z (goo x y) err (format "goo %s %s failed" x y)]
      (qux x y z)
      (handle-error err))
Where `let-every` is a macro that works like let, but stops short on the first nil/false variable, runs only the next symbol binding expression, and then runs the else-clause.

There'd be nothing special about the "err" symbol on each line. It's just the next symbol binding, but on the same line as a convenience, and this means it can reference any previously valid symbol bindings.

Here's a quick & dirty implementation of that macro. I don't have a Clojure interpreter installed, so I don't know if it works.

    (defmacro let-every [bindings if-body else-body]
      (let [pairs      (partition 2 bindings)
            quad-pairs (partition 2 pairs)]
        (loop [quad-pairs quad-pairs]
          (if quad-pairs
            (let [[quad-pair]         quad-pairs
                  [try-pair err-pair] quad-pair
                  [try-sym try-expr]  try-pair
                  [err-sym err-expr]  err-pair]
              `(if-let [~try-sym ~try-expr]
                 (recur (next quad-pairs))
                 `(let [~err-sym ~err-expr]
                    ~else-body)))
            if-body))))
Given the above example, it should expand to this:

    (if-let [x (foo)]
      (if-let [y (bar x)]
        (if-let [z (goo x y)]
          (qux x y z)
          (let [err (format "goo %s %s failed" x y)]
            (handle-error err)))
        (let [err (format "bar %s failed" x)]
          (handle-error err)))
      (let [err "foo failed"]
        (handle-error err)))


Given the existence of exception handling, I would rather apply the pattern:

   ;; Plain old ANSI Lisp

   (let* ((x (or (foo) (error "..."))))
          (y (or (bar x) (error "bar ..."))
          ...)
     body)
I mean, if, in the end, we are going to "error out", rather than just forward-propagate `nil`.

If we are going to just propagate `nil`, then if we have an `iflet` that tests the last variable, we can do:

  (iflet ((x (expr))
          (y (if x (bar x))
          (z (if y (foo x y))) ;; z is tested by iflet
    ...)
it's just a bit verbose. That can be condensed with a simpler macro that doesn't have the err stuff.


Your solution doesn't short-circuit when any of the calls fail. It relies on (error) throwing some kind of exception to interrupt control flow and prevent the following lines from executing.


Yes; I'm using ANSI CL syntax/semantics. error denotes cl:error which can in fact be relied upon to throw. That's what I mean by "given the existence of exception handling ...".

Substitute your favorite dialect's error thrower.


I wrote something like that several years ago: https://github.com/egamble/let-else


I really like `:delay`. It will allow efficiencies in my already Haskell-esque "define all possible values used in a single big let" style I program in. My code all handles nil safely and returns nil when appropriate but it would be even better to avoid evaluating things unless used in the body of the else (or a later binding).


Really cool! May have to borrow this some day.


OP already conceded if these funcs return nil the solution is easy; the case considered was when the funcs return heterogeneous data


Or when you want to do logging. That's the part I was working at.


>Given the above example, it should expand to this:

I don't think that your expansion is correct at all. Maybe you have a misplaced quote somewhere, but this one is not making sense.


Having used the cats library in anger... I think for Clojure it'd probably be better to make a couple specific monads to handle, e.g., either-ing. Talking about monads abstractly really requires type support, I've come to believe. It can be done otherwise, but it gets really complicated.


I've been using the cats library myself: https://funcool.github.io/cats/latest/ and find it really useful.

I mostly use the Maybe and Either monads (and occasionally the exception monad if I'm calling some function in a third-party library that might throw an exception).


There is a neat macro I've been using to solve problems like the nested if-lets in the post:

https://github.com/Engelberg/better-cond

  (b/cond
   :let [x (foo)]
   (not x) (do (log "foo failed") false)
   :let [y (bar x)]
   (not y) (do (log "bar failed") false)
   :let [z (baz x y)]
   (not z) (do (log "baz failed") false)
   :else (do (qux x y z) (log "it worked") true))


Have you looked into the `some->` threading macro? https://clojure.github.io/clojure/clojure.core-api.html#cloj...


Yes, it gives you short circuiting on nil values. The post mentions `some->` and laments its inability to handle varying function signatures.

You can combine `some->` with other threading macros to make it achieve the desired effect, and you can also achieve the desired effect with `if-let` as the post demonstrates, but I believe the b/cond approach to be more readable than both.


After spending 50 months with Clojure, I can safely say it's my favorite server-side programming language (with Datomic as my favorite database), and the tooling (CIDER + Paredit + Emacs) is really downright amazing in terms of productivity.


Although the tooling is great, I found it super frustrating that you basically have to use the entire suite of prescribed tools to feel productive. Basically everything except emacs sucks with clojure, especially if you're a vim user. I found that pretty frustrating. Vim fireplace sucks. Then if you want to use a gui editor with a vim plugin you have to abandon the vim world and use editor-esque paredit plugins (since no one is developing vim-style paredit for vim-editor-plugins) gasps for air.

Then finally there's emacs which I really don't want to get started with.


I switched to IntelliJ + Cursive, and haven't looked back since.


Yes! 1000 times, yes!


I was a long-time vim use, and I found slimv to work pretty well with Common Lisp, but at the time I was never very interested in Clojure. Since then, I've switched to emacs using evil-mode to provide a vim editing experience: and, I have to say, evil-mode is almost better than vim itself. I've used evil-mode+cider to develop a couple clojure web apps and, I've been very happy with my setup.

http://paste.lisp.org/display/340427

(and, yes, I have tried spacemacs, but found it unpleasant)


Atom + ProtoREPL[1] + Parinfer[2] is surprisingly good for clojure, without having to learn a swath of form manipulation keybindings. I haven't tried combining those with vim bindings. This (https://gist.github.com/jasongilman/d1f70507bed021b48625) is more or less how I have it set up.

1: https://github.com/jasongilman/proto-repl

2: https://github.com/oakmac/atom-parinfer


For what it's worth, I'm a pretty happy vim-fireplace user for evaluating files and forms. I also happily use standard vim brace matching, text-objects, etc instead of paredit or similar.

For JVM development, I only bother with NREPL because vim-fireplace needs it. For library development, `lein repl` gets the job done. For application development, I copy/paste around just enough code to launch an nrepl server and run the Clojure jar directly with a vendored directory of jar files. No other tools needed really.


Yeah it's a very real form of lock-in, nothing else really compares to Emacs + Paredit + Cider for productivity. And Emacs isn't great. But when I did Clojure full time for 5 years, I just sucked it up and learned Emacs and got really good at using it. Customizing my init file little bits at a time probably added up to a month's worth of lost productivity. But out of 50 months, 1 month lost to environment setup isn't bad. That's like 2% of the whole time. Or about 48 minutes per week. And the learning curve for me (as a long-time vim user) wasn't as hard as I thought it'd be, especially since knowing bash shortcuts really helped prepare me for the Emacs way. In fact I even took some Emacs knowledge back to the shell, such as how Ctrl-underscore is "undo the last edit to the current command".


I am using https://cursive-ide.com/ since early public versions and nothing from vim or emacs world compares to this for Clojure.


I wrote a post detailing a couple of possible editor choices (but focusing on first-timers and people who only occasionally need to edit Clojure code while working on something else - I think Nightcode is a good choice in this case - so probably not directly applicable to you): https://klibert.pl/posts/tools_for_lisp_syntax.html

Still, the two screenshots there (of my Emacs config) may help convince you to give Emacs a try.


It's definitely not as integrated into Vim as in Emacs, but I use Vim exclusively with Clojure and have no real issues with it. For ClojureScript, Figwheel automatically hot reloads on file save, so there's no interaction with the editor at all. With Clojure, either lein-ring automatically reloads as needed, or I can eval the changed file in an open REPL with Fireplace.


I have gone down the same road. I settle with Intellij, Cursive is quite good and as a Vim user, I have vimidea which is pretty ok. Background: I find Vim or Emacs really slow when you develop big projects, Intellij is much more responsive which is exactly what I needs to get things done.


I use whatever editor at any given moment and then use (use 'my.testing.namespace :reload) in a repl in a terminal window. I have to watch for namespace collisions in the repl when refactoring into other namespaces and reloading those, but works really simply.


Just to keep piling on, intellij + cursive is pretty incredible.


I couldn't be happier with vim-fireplace, vim-sexp, and vim-sexp-mappings-for-regular-people. What about vim-fireplace is frustrating for you?


Highly recommend LightTable. its perfect for clojure.


Isn't it abandoned by it's author?


From someone that has never touched clojure ( or any lisp) i would say that the python equivalent using either/right libraries made me feel like running away as far as i could.

Could anyone provide me with an example of a code that would actually read nicer in clojure than in python ? Make it as arbitrary as you'd like, i'm honestly trying to understand . i've read enough about lisp bluring the line between data and code, so i'm starting to get an intuition of its benefit. I was just hoping to finaly see a real world example, and i'm a bit disappointed.


Nicer to read is pretty subjective, but I'll tell you why I've switched 2 projects from Python and Ruby: speed. Our systems just had too much to crunch through, and the global interpreter lock pretty much killed us. We switched over to Clojure in 2009 and never looked back. In the early days, we had to wrap a lot of Java, but it's been several years since that was necessary. Currently, Clojure + Kafka + Apache Storm is the scaleability trifecta.

One final comment, which is often tragically overlooked, in my opinion: ease of deployment. Package management and deployment often sucks in other languages (especially in Ruby). By contrast, Leiningen is a SWEET dependency management tool, and the built-on-Java approach leaves you with a jar that you scp and you're done.


I do most of my work in python, and don't plan on switching any time soon, but after playing with Clojure I sure do miss leiningen over the mess that is pip / virtualenv / conda / zomgihatepackagementinpython.


anecdotally, a lot of go's adoption was likewise driven by python and ruby programmers looking for speed while still retaining some "high-level language" features.


A side-by-side comparison of the implementation of some nontrivial software or algorithm between Clojure and Python would be misguided. These two languages have sufficiently different paradigms that a direct port of a nontrivial program from one language to the other would end up in a misuse of the target language's abstractions and idioms.

Clojure is a Lisp-like language and would be well suited to taking an input and applying successive transformations to it without keeping state along the way. Meanwhile, Python mixes imperative and functional paradigms, and managing state comes a bit easier. In particular, the Either monad is much more natural an idea in Clojure than it is in Python, and indeed does not suit Python well at all.

You asked for an example in which Clojure would "read nicer" than Python. Consider the task of being given a long string of (natural language) text and

1. splitting it into sentences,

2. tokenizing those sentences into words,

3. attaching part of speech tags to each word.

Implementing in Clojure, imagine we have ready-made functions called splitSentences (which takes a string and returns a list of strings containing sentences), tokenize (which takes a string containing a sentence and returns a list of words in that sentence), and tag (which takes a list of words and returns a list of 2-tuples whose entries are those words along with their part of speech tags). In Clojure, this might look like

    (map tag (map tokenize (splitSentences text)))
In Python, this might look like

    sentences = splitSentences(text)
    tokenizedSentences = [tokenize(sentence) for sentence in sentences]
    taggedTokenizedSentences = []
    for tokenizedSentence in tokenizedSentences:
        taggedTokenizedSentences.append([tag(word) for word in tokenizedSentence])
I've exaggerated a bit, but that's the idea.


I don't think there's any magical piece of code that is objectively prettier in Clojure than in Python. Syntax is just syntax. Honestly, the homoiconicity of Lisps is nice and all but it's not something that often matters in practice. The syntax looks funky for the first three days you write Lisp, then it just fades into background like with most other languages. I guess Lisp grammar is particularly elegant because it's so small but still allows great expressive power. It doesn't get in the way.


You're not insane. You will be hard-pressed to find a side-by-side comparison of Clojure and Python. I say this for two reasons:

1. A program in Clojure should not necessarily look the same as one in another language. Oftentimes the overall program architecture is going to be very different.

2. For the cases where you can translate certain concepts (example, casting a string to an int) -- oftentimes Clojure code might come across as pretty hideous and verbose.


I'm not that familiar with Clojure but I found the following example that seems more real world-esque than the 4clojure.com resource posted below:

https://adambard.com/blog/PHP-ruby-python-clojure-webapps-by...


That Clojure code looks like what I would write: non-idiomatic and procedural. EDIT: greyrest's code looks more idiomatic and thus way shorter but still understandable.


I have been using Lisp languages, off and on, since the 1980s and generally like them. That said, if you like Python and are effective with it, there is no reason to try Clojure unless you want to for fun. I have had to use Python a lot in the last year for Tensorflow and it is certainly a nice enough language.


Read "On Lisp" by Paul Graham http://paulgraham.com/onlisp.html


There are a wealth of information about lisp variants, clojure, and functional programming. If you did some simple research you would not be so disappointed.

Lisp has been around decades, surely you can find some "real world" examples that you can learn from.

Personally, I started reading the clojure documentation and going through 4clojure https://www.4clojure.com/ , a kind of simple programming introduction problems to clojure

People have solved all the questions, and you can find them online really really easily

https://gist.github.com/SegFaultAX/3607101 for example.

Just do some research, and come back once you have done a basic amount of work, and can no longer say "I've never touched clojure".

Instead, come back and say "I did some research, I worked through some 4clojure questions, and I still don't understand..." then I think you'll get a more productive discussion.


Clojure is the default go to language for me over the last 5 years. It is not even funny how more productive Clojure is compare to Java if we are talking about JVM hosted languages. There are obviously shortcomings of Clojure, missing Either and so on, but you can always implement these simply, worst case with a macro. It is good to see libraries like cats though.


This was a great read. My favorite part was the section about rarely used language features, as I have had a similar experience. It makes you wonder if you're inexperienced or doing something wrong, when in fact those features may just not be needed most of the time.


Having talked to a bunch of very experienced LISPers recently (including Richard Gabriel) about this very topic, it appears to be the case that most people go wild with macros for a little while, but this passes and then you write fairly straightforward code.


I've written CL macros. I've noticed that for me, the probability of long-term use seems proportional to the size of a macro's implementation. The more work a macro does, the less likely I'm to revert to "straightforward code".


There is no single answer for that. Some code from experienced Lisp developers uses a lot of macros. Others don't like that style. It also depends on what you are writing.


Most of the macros you'll need are already part of clojure core, thankfully


Well, those feature are needed, but good people already made use of them. The "go" macro from core.async is a good example.


can be said about C++ (or any language). But he (the author) said it much better.


(sorry YOU said it much better.. )


The author briefly touches on schemas. Anyone have experience or recommendations working with clojure.spec? If I were using Clojure with a schema less database, would I stand to gain anything significant?


I've been using plumatic schema: https://github.com/plumatic/schema in conjunction with compojure-api. It provides a nice way to validate data coming into my REST endpoints. I'm currently using Postgres, but I'd probably still make use of schema if I were going NoSQL.


> Nevertheless I definitely emitted some crappy code in my first few months. Stuff like:

    (every? #(= % “success”) (map :status (:state task)))
> Which I’d write like this today:

    (->> task
         :state
         (map :status)
         (every? #(= % “success”)))
While I love threading macros, I'd argue in this case readability and comprehensibility between the two examples is more or less a wash.

Personally, I find the former slightly easier to grasp at first sight. The second could be improved (again, in my personal opinion) by combining the first two lines: it seems unnecessarily granular to say, essentially, "we take a map called task, and then we get the value of its :state attribute", rather than just "we get the value of the :state attribute of the map called task".

Both could be made terser via the set-as-predicate idiom:

    (->> (:state task)
         (map :status)
         (every? #{“success”}))


What's the state of static type checking in closure? Is it accepted among the community? My uninformed impression is that runtime checking via specs won?


Clojure's been my go-to language since around 2009, and I use it full-time today. A few points from the article:

The horrible if-let code the author shows is exactly the kind of thing people use macros for. I'm surprised nothing exists in clojure.core for this, but every project I've ever worked on has something like if-lets, which short-circuits on the first falsy assignment. Everyone thinks they don't need macros and then finds stuff they hate in the language which is easily fixed by macros. This is probably true of the non-mainstream paradigms in all languages. It takes some willpower and humility to learn all Haskell's lens arrow operators instead of just laughing at them.

clojure.spec is nice, and I use it in my current project, but it has performance issues (it is after all basically alpha code) and I'll be honest that I don't _really_ understand what the workflow is supposed to be. If you deal with a lot of side-effectful code you might be using conforms and asserts to keep things sane, but I'm not really benefiting from the generative testing bits. Thinking in terms of generators for code that _is_ pure just feels like another job that I've for some reason forced on myself. Anyway, at the very least, it does make the world of procedural code operating on maps a little safer and saner and you should probably try it.

My complaints about Clojure are nothing to do with the language, which I think is absolutely fine. My main complaint is that Clojure people are smart, and fundamentally believe in small libraries over frameworks, and this means that the Clojure landscape is scattered with abandoned overly-specific libraries, and abandoned and unloved big frameworks. If I could, I would pay someone full time to maintain Incanter, but it's now basically dead, and that means Clojure has no useful numerical computing environment. I am lucky that I can get by with what already exists in Incanter and what's in clj-ml for my analytics work, but nobody is going to quit Python or R for Clojure at this point unless they're smart enough and willing to write all this for themselves.

I happen to do some web stuff and I think ClojureScript is about as good a language as you're ever going to get on the browser. But the other consequence of a community where most people are smarter than you is that the best front-end frameworks are utterly inscrutable. Om has wonderful functionality that I think I would like in my app, but I am too dumb to understand all the moving parts. Reagent is very elegant but is tougher to scale to complex web apps.

I still believe that Clojure is a beautiful and productive language. Year after year I see other languages pile on new syntax while Clojure just remains brackets and functions. For the parts of my code that are pure and functional it's wonderful, for the parts that are ugly and pragmatic I have the full range of the JVM landscape available to me. I get to stay in Emacs, and Cider me custom elisp does everything I need for an IDE. But if anyone asked me to recommend a language to spend their lives in I would probably just say Python and if you do web stuff JavaScript. Bigger community, better maintained open source projects, and just a clearer roadmap to mastery.


WRT Incanter, you might find some high-performance libraries interesting: http://neanderthal.uncomplicate.org and http://github.com/uncomplicate/bayadera.

They are much more low-level than Incanter, but also orders of magnitude faster, and intended as a foundation for the Incanter-like libraries to be built on top.

They are actively developed.


Re incanter I fully agree - it also works for all my use cases (well, if something is missing and you know what you want, you can usually write it on your own using stuff incanter does have, did this eg for Logistic Regression) but I think it has so much potential.

For some time I expected a Java lib to appear but whatever came was always forcibly distributed (Spark, Mahout) and/or you had to first instantiate an AnnotatedDistributionParameterFactory which instantly makes me want to puke. So there we go, incanter is not maintained but probably the best you can get on the JVM :-/


I do recommend clj-ml if all you need is a set of sensible ML algorithms (in this case based on Weka):

https://github.com/joshuaeckroth/clj-ml/

The only reason to tolerate a language as terrible as R is that it has such astonishingly broad library support - there is basically no algorithm you can find on Wikipedia that doesn't have some sort of R implementation. Incanter will never have this, and tbh it's very difficult to create outputs as pretty as themed ggplots. For very simple statistical analysis Incanter's okay (and I've seen smart people build lots more on top of it), but even really basic model-fitting stuff isn't built in, and if you're not smart or just don't have the time, it'd be mad to commit to that environment.

It's a shame because structurally it's quite nice, and I did buy into the idea that Lisps were well suited to this sort of work, especially being very REPL focused.


I never really took a look at Weka since I am using the results commercially (or rather my clients do) and I think I "distribute" that software.

Now maybe I could make the case that I am not distributing the software but I write it in their name and on their bill. In other words, I can use it as long as my job is to write software (for them) instead of creating a product that I will sell.

Still, I prefer to stay away from it as long as possible but I agree it will be hard for some tasks such as decision trees which are unfortunately a very nice fire and forget algorithm.

There are people claiming that you can do most of classification and regression using logistic and linear regression, so I try to stick to that as long as possible and incanter allows for that.


For Frameworks, I have started to look into and contribute to http://arachne-framework.org/.

Its a 'web framework', but really its just a module system. It tries to be a get started fast and still be able to do really complex stuff kind of framework.

I am running into the same issue in Production and I hope to use Arachne to make this easier in the future.


Have you tried re-frame? It's a framework on top of Reagent that I've found super enjoyable to work with.


Yeah, I find re-frame a little clunky if I'm honest, e.g. extending/intercepting fx is hard, the balance of local ratoms vs global subs/handlers is difficult, there's no real enforcement of layering so you have to work hard to get your subs/events at the right level etc. I dunno, I understand the aims and I use it on a couple of things, I just think it loses the elegance of Reagent without really giving you the scalability of Om. I'd have to spend more time on a few greenfield projects to get a decent picture though.


Yes, unfortunately the Incanter development has stalled :-(

David isn't involved into it for many years, I also don't have free time last 2 years due my involvement into very big projects.

I would happy to accept your contributions into Incanter's development...


I have over one year of clojure experience on customer projects, spread out over the last six years. I like the language a lot, but except for my cooking website (cookingspace.com) and the server side code for an Evernote clone prototype, I don't use Clojure on my own projects where I tend to use Haskell for the fun of using Haskell and then Ruby and Java are my go-to languages to get stuff done quickly.

I agree with most of the author's opinions about the use of Clojure except that personally I dislike using multimethods.


Seasoned Clojurians: is

  (every? #(= % “success”) (map :status (:state task)))
actually considered crappy code? It looks perfectly natural to me, but my Lisp background is more generic.


No, not at all.

The threading macros are more useful when you have more nested code. For example:

    (->> coll
         (remove nil?)
         (filter :available?)
         (map :stock)
         (reduce +))
Is perhaps a little easier to read than:

    (reduce +
            (map :stock
                 (filter :available?
                         (remove nil? coll))))


I had the same reaction. I much prefer this to the threading macro.


crapiness comes more so on the readability/presentation aspect.


I only ever monkeyed around with Clojure, but on their first example: is it weird I'm OK with both?

The only thing that surprises me about both of those examples is that :state is a collection of things not just one thing, but I imagine if you're more familiar with their structures that would be less of an issue.


(author)

I just grabbed an example at random there. It's not really an excessive example of nesting, but, at the time I didn't grok threading.

I think the data being manipulated is a cloudformation response or something, so, the structure isn't something I'm in control of.

I actually changed it from `(:status (:status ...` in the original, which is even dumber naming, so that it wouldn't look like a typo. Real life programming is thoroughly unglamorous I guess.


I think that there is something more fundamental underneath this experience report that is worth remarking upon but that isn't mentioned in the article.

>If I were going to give you a quick summary of what our codebase is like, I’d say it’s procedural code that manipulates maps.

>One thing we do use more extensively are multimethods. We use this to dispatch asynchronous jobs in our workers, to fan out to different handlers from webhook endpoints, and to make transitions in our deploy finite state machine.

>... sometimes you find yourself boxed into writing non-idiomatic Clojure. A good example of such a situation is dealing with a morass of heterogenous functions that can return error codes.

Languages based on different paradigms excel in different situations. Lisp-like languages such as Clojure (and, more generally, functional languages), have abstractions perhaps most well suited to "pipeline" situations - take an input, apply transforms A, B, C, ... in sequence until the output is returns, bailing out if there is an error in some stage. Multimethods, which the post mentions, are a good example of this: given input X, send it off to Y based on some conditions. In software applications which have to do this sort of work, it's a good fit.

If there is complex state to be managed under many different conditions, however, these paradigms can be cumbersome to deal with.

>It is unclear to me if the category theory would still be a win on a less experienced team. I have a long history of being skeptical of things like this, but it has improved our lives recently.

The author mentions realizing that the Either monad well embodied the nature of the algorithms being implemented. Speaking from my own experience, I would suggest that teaching a team implementing the sort of software it seems the author's team was implementing would be a worthwhile investment - but only if it was reasonably clear beforehand that those abstractions would be well suited to the problem at hand.

Perhaps the most important skill to train a team up on, in my opinion, is the ability to recognize the type of problem at hand well enough to see what abstractions would be well suited to it and thus what language to use. In other words: it is very important to choose your tools well.

If I could offer some advice to the author (and others in similar positions) regarding the question of investing time in training a team in any set of abstractions, be it concepts in category theory for the sake of Clojure or something else, it would be this: analyze the nature of the problem at hand, make a decision on what tool (language, framework, whatever) you feel would be best suited, familiarize yourself with its world if you haven't already, and then have a meeting with your team in which you concisely explain that analysis and the reason behind your decision. Mention why other choices would not be as well suited, but, CRUCIALLY, also give examples of situations in which the other tools you considered would have been the best solution. If you have and reservations about your choice, explicitly state them. (In fact, you may be surprised by someone on the team having had experience in the domain at hand but not having mentioned it because it hadn't become relevant yet!)

This will not only teach the reasoning process but also give the team some information about what alternate approaches to consider when faced with a problem unlike the ones they have seen before, especially if the team is junior.

Furthermore, sometimes one discovers that the nature of the problem being worked on is, in fact, different from what it was originally understood as. It is a lot to place on one lead to always be vigilant about that, and self-doubt can creep in. If you transparently expose your thought processes to your team, you may find yourself surprised by eventually having a team member saying to you, "don't you think that this other tool/approach you had considered may actually be better suited to <situation> now"?

It is a lead's job to lead, but it is also to share as much of their knowledge, experience, and reasoning process as possible with the team. Sometimes we go down the wrong route at first, but everyone on a team should understand the why and what of every point along the way. As a lead, I make it my goal to train everyone under me well enough to replace me and to feel comfortable enough to voice any commentary on the decisions I have made.

I was going to go on, but I think I went off on a bit of a tangent. I'll leave off here.


> ...functional languages), have abstractions perhaps most well suited to "pipeline" situations - take an input, apply transforms A, B, C, ... in sequence until the output is returns

That's very true. Glad to find out I'm not the only one who perceive it this way.

This is the reason I believe multi-paradigm languages are more practical(or better suited to solve real-world problems) - as they allow you to utilize different approaches for different problems. At the same time, being too wide/generic seems to lead to lacking of some expressiveness single-paradigm languages could provide (like lack of native pointfree style).


ah nesting, threading and monads.


> Why not? If a language's goal is to be practical (as is Clojure's), then it should take practical concerns into consideration. This is something that the Java community definitely gets right.

AHAHAHAHAHAHAHAHAHAHAHAHAHA!

Java getting practical considerations right. Oh, I so needed a good laugh.

Let's start from the basic:

No unsigned type. Bit/byte manipulation code written in Java is garbage when written by experienced programmers. When written by inexperienced programmers it's a nightmare.

Ever looked at the "class pyramid" monstrosities in Java? That includes the two primary GUI systems.

Ever debugged all the stuff coming from the type erasure that you are stuck with doing in Java?

And, let's not even gets started on the people who think they can write concurrent code (Hint: if your Java concurrency solution doesn't involve java.util.concurrent-you're doing it wrong.)

While Java gets many things right, it's ability to prevent people from falling into pitfall after pitfall is NOT one of them.

I would argue Clojure does a much better job at this simply by the fact that immutability is much more of a default choice.


> AHAHAHAHAHAHAHAHAHAHAHAHAHA! Java getting practical considerations right. Oh, I so needed a good laugh.

Please don't post like this here. It degrades the community, takes threads into flamewars, and discredits what you're arguing for.

We detached this subthread from https://news.ycombinator.com/item?id=14008093.


Why do people here try so hard to be condescending sometimes? Laughing and mocking does not help when prior to it others tried to provide earnest replies, based on experience and presumably reality. Please save the vitriol for arguing with those reason does not work on.

Your points do have merits in showing Java is imperfect. I would even say that it is deeply flawed. I would not agree it is impractical.

The simple fact about the practicality of Java can be shown in the millions and millions of lines of code powering much of the boring business infrastructure in the United States. If Java disappeared overnight Banks, Insurance companies and much of the rest of the financial sector wouldn't open the following morning (And then we look at our phones).


The same would happen for COBOL. That alone doesn't make it practical or show its practicality, it only shows that it has actually been used in critical systems, apparently with some success. Many programming languages can claim that.

Honestly had the same reaction as above. It's a hilarious laughable claim that the Java community gets this nebulous "practical concerns" thing right. Agree it's not a helpful response here but I don't blame them for falling to it. It's not exactly "forced". Probably better for @shit_hn_says though. Edit: the much less nebulous claim by 'tlarkworthy is much better.


Sticking to the technical discussion. I agree that some businesses would have to close for Cobol. That number goes down each year, with Java it is still going up. I just the picked the financial sector because we all know about banks and most of us have insurance, so its visible. I also worked a contract at Nationwide (who could do limited business without Cobol, but not without Java) and got to see both in operation. There are many less visible things that would fail but could have more impact.

Without Java the US military is in trouble, a majority of cell phones stop working, the printer in the office I am now fails, Netflix has some level of problems and more and more... It is debatable that Java is the single most widely deployed programming languages. I say debatable, because many of those JVMs were written in C and many of the kernels (when they need a kernel) JVMS run on are written in C, where do we draw that line.

If Java were not practical this would not have been possible. I suspect it would have looked more like Cobol, with shops of experts being able to run and maintain it, but failing and migrating away as time went on.


There's not much technical discussion to be had when our views of practicality are so different, and practicality itself so subjective and context-dependent. If Java starts to smell like COBOL across the wider industry (not just HN types) will it no longer be practical, because it no longer satisfies the narrow criteria "has been deployed in critical applications and is not being migrated away from"[0]? What about the great tooling/perf/backwards compatibility/ecosystem mentioned, do those not matter at all for practicality concerns? I'm not sure. Maybe we'll just agree that practicality is a low bar to satisfy and shouldn't be a high concern for much? Reminds me of the discussions around WorseIsBetter, and a quote from Wayne Mack[1].

[0] I've heard from COBOL forums that the amount of new COBOL written each day and existing in the world keeps increasing; they've even got Object-Oriented COBOL now. I don't think it's unreasonable to assume that if COBOL had the same market pressures Java does (plus university pressure) and if the world was basically stuck with it in the same way we've had Java this century that we wouldn't be able to push it to service our needs.

[1] http://wiki.c2.com/?TheSourceCodeIsTheDesign "I believe it is time to explicitly state the long held secret of software, we do not need to do design; design is ineffective and costly."


If you follow upthread, cle asserted was that a language should be held responsible for being able to be misused badly.

And then asserted Java as a counterexample that is better about this.

I'm sorry, but anyone who has had even tangential exposure to Java codebases would find this equally laughable.

Java codebases are by far the worst codebases I have ever had to wade through. They even surpass C++, and I loathe C++. I presume it's the fact that you can cut/paste StackOverflow until something works in Java while C/C++ blows your foot off a lot earlier.

To be fair, I have not had to wade through large COBOL or assembly codebases, so my experience may be skewed.


Those are snooty language level criticisms, pragmatists care about tooling, performace, talent acquisition, backwards compatibility and ecosystem. All of which Java is pretty stellar at.


And yet people with the capability to think about whether or not to use Java (as opposed to having it pushed on them from above) seem to choose anything else unless they have very specific circumstances.

And, if I prioritize your list, I arrive at C#/CLR, not Java/JVM.


Hint: if your Java concurrency solution doesn't involve java.util.concurrent-you're doing it wrong.

Of course pretty much everyone's concurrency solutions do involve j.u.c since Java 5. They are truly powerful language facilities.


Gee, so why do I STILL see so many programmers writing "synchronized" all over their Java or Android code?

I wish I were joking. HN is not like the real world.

This is just like "git/hg is superior" arguments. Those of us in the real world are still happy when we see a business actually use source control of any form.


Using synchronised and j.u.c aren't mutually incompatible. It's quite easy to use synchronised to conservatively control access to the state of a single object, and often that's all you need. It can affect performance depending on your access patterns, but it's not like synchronised is deprecated or anything.

When was the last time you saw wait/notify? I'm willing to bet that was a long time ago.




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

Search: