Hacker News new | past | comments | ask | show | jobs | submit login
Why I like Clojure (sulami.github.io)
237 points by tosh 51 days ago | hide | past | web | favorite | 153 comments



My favorite thing about Clojure:

You compose libraries, instead of totally buy into framework A or B. Some people prefer the opposite.

However, Composition works in Clojure because everyone is using the staples: functions, maps and seqs. Pretty much everything in the language can work as one (or all!) of them.

While I also like static types, this is my argument for a well designed dynanic language like Clojure or its Lisp relatives: stuff just snaps together like magic lego bricks.


Composing libraries instead of buying into a framework is a wonderful thing. It allows a developer to use just the parts they want, integrate them with just what the developer needs, and get on with whatever they're doing. It's a beautiful idea, and it can enable amazing levels of productivity.

With that said, might it also be possible that there could be some drawbacks? Having worked in and with Clojure, I found the ecosystem incredibly immature. It's somewhere beyond arrogant - reckless comes to mind - for most engineers to do all their own plumbing for a web service. The odds that they'll manage to design someone easily long-term maintainable are slim. The chances of authorization, authentication, potential SQL injection, and a thousand other security things being handled well is effectively none - the Clojure ecosystem often seems wildly ignorant of such things. The core philosophy of just composing the libraries you need with Ring means that anything you don't actively think of will be likely entirely neglected. This is a dangerous way to ship code that will be facing the real internet. Clojure requires you to fit absolutely every aspect of everything you're doing into your head and be handled by you, because nothing is going to do it for you. The lovely magical LEGO feeling is, sadly, a lie.

This stands in stark contrast to the level of consideration around maintenance and security that has gone into a modern, mature framework. Or a modern language where SAST is an option.

Doing low-level stuff feels absolutely amazing! The sense of control, of having your hands in the guts and the ability to do what you need without wasting time on bullshit magic, is heady and glorious. It's just perhaps worth considering that that magic can sometimes be incredibly valuable, well worth the tradeoff against the feeling of freedom.


> I found the ecosystem incredibly immature

This does not match my experience at all.

There is a thing called sane defaults. You solve a problem by decomposing it to simple elements, and then combine them to sane defaults, while still giving full power to your users. Examples of sane defaults in areas you mention:

- authorization/authentication: buddy (https://github.com/funcool/buddy) has both core library with crypto stuff and complimentary libraries for auth etc. For example, if you want to securely hash a password, you don't have to come up with salting yourself, just use buddy-hashers: http://funcool.github.io/buddy-hashers/latest/#quickstart

- sql: clojure.java.jdbc and it's successor next-jdbc for sql queries use vector with query string containing question marks for arguments and then arguments that get escaped, see select example here https://cljdoc.org/d/seancorfield/next.jdbc/1.0.6/doc/gettin...

- "other security things": ring site defaults, see https://github.com/ring-clojure/ring-defaults/blob/master/sr...


> There is a thing called sane defaults. You solve a problem by decomposing it to simple elements, and then combine them to sane defaults, while still giving full power to your users. Examples of sane defaults in areas you mention:

You're right! Sane defaults are incredibly powerful and let developers make the right decision by not making decisions at all.

When developers have to stop and remember to integrate a library for authZ and authN, and then integrate individual modules from it for their use case, some might opine that we have gone beyond the bounds of sane defaults.

Sane defaults for Clojure in a web context are a wonderful idea. It's unfortunate, then, that buddy is less sane defaults and more a toolkit from which someone assembling a framework has the option to choose sane defaults.


I did also find the ecosystem to be not-so-mature a few years ago. Compared to something like Rails or various Java collections of tools it feels like things are less available or simply a poorly-made Clojure veneer over and underlying Java tool.

Ecosystem takes a while.

(Yes, I was using both JDBC and Ring as well as other things like Leiningen. Core.async was a notable exception as something done better in Clojure than elsewhere)


Thank you for the viewpoint. It certainly has merit, but there is also the possibility of "metapackages" like luminus [1], a curated package of libraries that work well together. It's easier to make a framework in the land of libraries than vice versa.

Some of what you perceive as immature may be that the community is small, and a lot remains to be done.

[1] http://www.luminusweb.net/


You're absolutely right, it's easier to make a framework in the land of libraries.

With that said, it may be more difficult to get one used when the culture of library-land abhors frameworks. The Clojure developers I've worked with so far mostly seem repulsed by the idea of using a framework.

This leads me to fear that the ecosystem is likely to remain in the hole it has dug for itself. Possibility is wonderful, as you say, but it is unlikely to save applications being written today.


> repulsed by the idea of using a framework

I think this is one of the memes in the community which is starting to be reconsidered. You're not alone in seeing these downsides, and people are starting to write about it more (code and words). Things like Fulcro or even Datomic Cloud are very interesting developments in this regard.

[1]: https://jacobobryant.com/post/2019/grow/ [2]: http://flyingmachinestudios.com/programming/why-programmers-... [3]: https://github.com/fulcrologic/fulcro


This is a great conceptual point that I think can be missed by folks who prefer a constellation of quality library-style code over a single framework. It's true that the *nix ecosystem is a case of the former over the latter, and I think that can work nicely for library level code that is used to build applications. But, it's hard to build protocols; or maybe, more accurately, it looks like successful ones emerge rather than are constructed.

The closer you get to the application layer, the more it looks like high quality, opinionated frameworks and ecosystems are great. You get more mileage out of a sophisticated foundation once you're at that point, so your integration risk surface is higher if you choose to roll your own framework. To some extent, this is unavoidable, but I've always gotten value out of minimizing it.

To some extent, I think Clojure has some great things going for it because it can hook into the JVM and the Java package ecosystem. But if that's ignored, then it's essentially starting from scratch.

For folks who have production experience in Clojure: how valuable is Java interop in your experience? How often are you able to get away with using the most idiomatic parts of the Clojure-land ecosystem, and how often do you have to reach for Java-land? How easy is interop in terms of traceability?


> For folks who have production experience in Clojure: how valuable is Java interop in your experience? ]

Very, there a lots of high quality Java libraries, and interop makes it easy to use them. Especially when integration with other software (databases, message queues, etc) being able to use the Java client libraries is a blessing.


> how valuable is Java interop in your experience?

Clojure is designed to be a hosted language, and so Java interop is first class and integral to it. In fact, the standard library specifically excludes anything that overlaps with Java's, and is designed to complement the existing Java standard library.

> How often are you able to get away with using the most idiomatic parts of the Clojure-land ecosystem, and how often do you have to reach for Java-land?

It's not really like that, they complement each other and work in a symbiosis. Yes, sometimes it feels like the worst part of Clojure is Java, but not reinventing the wheel and leveraging battle tested Java libraries and tooling was a very smart pragmatic decision, so it's also the best part of Clojure. Just know using Java from Clojure is very idiomatic in of itself.

> How easy is interop in terms of traceability?

Pretty much seamless.


Pedestal is a web library that ships with security enabled by default.


Really though, Clojure web apps are at the php-no-framework level security days, not joking.

The most used HTML templater(hiccup) doesn't do any secure output escaping, bad password/session management, auth*, sql injection, bad encryption methods, its all there.


Hiccup has functionality to perform escaping with `h`. It doesn't do it by default though, for performance reasons and nesting of Hiccup in Hiccup. Normally, you'd just want to sanitize right before returning your response.

Doing it that way you can also perform more selective sanitation if needed.

Hiccup2 does sanitation by default, still in beta though.

The common SQL lib is java.jdbc or next.jdbc and they use prepared statements by default. Can you explain more where you see common risks for SQL injections?

I'm not sure what you mean by bad password and session management? Normally, the common lib people use is ring-defaults https://github.com/ring-clojure/ring-defaults what about these do you feel are insufficient? Maybe do a pull request about it.

Auth and encryption I agree, but the untold reason is that people just use Java bountycastle and Apache Shiro or other for that. Buddy is the most popular Clojure one https://github.com/funcool/buddy , but honestly, for that stuff, you want something super battle tested, that means popular, and Clojure as a whole isn't popular enough for that, so really just pull in a Java lib done, it works flawlessly within Clojure.


> Hiccup has functionality to perform escaping with `h`. It doesn't do it by default though, for performance reasons and nesting of Hiccup in Hiccup. Normally, you'd just want to sanitize right before returning your response.

One of the things that Rails' templates gets completely right is that it knows when something is nested and when it's going to be returned immediately. It is thus able to do its automatic escaping right before returning a response and not before.

At this point in history, I think this is the baseline behavior all templating systems should meet. Requiring users to remember to manually escape every output every time is begging for exploitation. Such a design error only strengthens the case that hiccup is not appropriate for serious use at this time.


It seems Rails used to default to not escaping as well, but in Rails 3 they switched to escape by default. And from what I'm reading, nested helpers do suffer from the over escaping, and in fact they warn not to blindly html_safe them. But I admit, I can't find very new guides on latest versions, are you saying ERB now tracks nesting and only escape once at the end?

That's actually what hiccup2 does as well by the way. I admit, it is a nicer behavior, and I'm not sure why hiccup2 hasn't gained more traction since it released, but you are free to use it, it's part of the normal hiccup release, just require hiccup2 instead of hiccup.

But hiccup is not the only popular Clojure HTML templating lib. Selmer, Clostache and Stencil which are much more ERB or Django like, both HTML escape by default.


You're right about Hiccup, but I don't understand the rest of your post.

What common SQL solution in Clojure are people using that introduces SQL injection, for example?


HTML templater has nothing to do with password/session management, auth, sql and encription, if your does, it does the wrong things.


I think you spliced that sentence wrong. It's bad templating, /along with/ bad session management, auth, sql, and encryption tools.


Do you have any evidence to support these claims? I've never had such problems, please see my other comment about similar concerns: https://news.ycombinator.com/item?id=20848195


I don't because I'm only speaking about how one should parse the sentence, not the facts of the sentence.


Same here. Most software I like is also a set of composable objects. A bit more difficult to understand initially, but much simpler and more resilient in the long run.

Examples include Emacs, Linux (especially distributions like NixOS or Arch) or classical Unix utilities.


composing libraries is amazing but it leads to time investment to read about said libraries and find the ones that you want.

That said Hickey gave a tiny argument to lessen the pain, the cost of freedom is often lesser than the cost of being jailed by a framework that cannot evolve


I love the language. I’ve never found a language where I found the learning curve so shallow for being able to do so much.

That said, I do find myself pining for the ability to easily compile a static executable (maybe with a small embedded runtime). Some time ago there was an attempt to port Clojure to Gambit Scheme, but that appears to have fizzled.

More recently, I’ve seen Ferret, which compiles a subset of Clojure down to C++. Also Joker, which is a Clojure-ish interpreter in Go.

Then there’s GraalVM, which I don’t trust, because it’s Oracle.

I feel like the relative lack of traction for non-Java / non-Javascript implementations is part of the brilliance and sadness of Clojure. Namely, the JVM is a rocket ship and a boat anchor.

Racket (modulo the uncertainty cast by the Racket2 discussions) and Gerbil Scheme both have a pretty good “distribute an executable” story while trying to introduce some creature comforts beyond plain Scheme. I suppose my ideal, would be something a little more like Clojure in terms of language, and a little more like Scheme in terms of deployment options.


GraalVM CE is licensed under GPLv2 what’s not to trust ?


I went down the “small standalone executable rabbit hole” a while back, with multiple flavors of Scheme.

Here are my notes on that: https://taoofmac.com/space/blog/2019/06/20/2310


most of the “port clojure to X” that I’ve seen are just top level syntax and don’t port the functional data structures over. Without those, I don’t see the point.

Beyond that, the land of libraries has sneaky little java dependencies scattered throughout. Clojure without the libraries isn’t very compelling.

As someone else mentioned, graal does a good job of making an “executable”


Compiling an static executable is also interesting to me. This is why I'm learning Common Lisp. Its syntax and semantics are not as beautiful as Clojure's (IMHO) but CL also has other powerful features.

I recently read about RUtils [1] which uses reader macros to modernize a bit the syntax.

1: https://gist.github.com/vseloved/9c6e36f2fa89f4accf3f3cbc371...



Why not Chicken Scheme? It compiles down to C.


Oh, definitely Chicken as well. The library story with Chicken is also pretty decent.


What I like most about Clojure is the interactivity: I can quickly test and iterate on ideas in my editor connected to a REPL. To me, this is the main advantage of Clojure.

Clojure's focus on the REPL, immutability and simple datastructures make it possible to inspect data and redefine functions in running systems.

In other languages I spend a lot of time making small edits, setting breakpoints, starting the debugger, waiting for the program to start, etc, where in Clojure I can just redefine and inspect stuff immediately, allowing very short feedback loops when programming.

A lot of Clojure tooling emphasizes short feedback loops: component with it's reload function to restart systems, figwheel/shadowclj with live reload of browser apps, which is a huge productivity boost.

Once I got used to the quick feedback loops that Clojure provides, all the other languages feel bulky and slow to develop in.


> homoiconicity, meaning code can be represented as a data structure inside the same language

> generating strings and passing them into eval like for example in Python

Python has become a pinata for language enthusiasts? Python lets you pin a decorator on a function, get the AST (a data structure AFAIK), manipulate it in a sane fashion in Python, compile it, return the result. No string munching eval require

I'm all for Lisp but this just isn't convincing


While you can manipulate Python's AST, you can't do it in a way that looks like adding new syntax.

In Clojure you can write a list comprehension as `(for [x (range 10)] x)`. In Python you can write it as `[x for x in range(10)]`.

Imagine these didn't exist, and you wanted to implement them yourself.

In Clojure this is possible. Its list comprehensions are implemented as a Clojure macro: https://github.com/clojure/clojure/blob/b98ba84/src/clj/cloj...

In Python there are several problems. The first is that you can't easily pull the decorator trick on arbitrary expressions. Not even with a lambda function, at least without doing a lot of work - inspect.getsource doesn't handle those properly.

But let's lower the bar, and replicate `l = [x for x in range(10)]` instead. You can do that with a decorator and an ordinary function.

Then the problem becomes that you can only use existing syntax in the function body. Syntax that already means something else. If you put an ordinary list comprehension in the function body then you get a syntax error when the file is parsed, long before the decorator has a chance to do anything. So you have to make something up yourself.

You might end up with something like this:

  @list_comp
  def l():
      x: range(10)
      x
Or, of course, this:

  l = list(map(lambda x: x, range(10)))
Neither of these are as convenient or readable as real list comprehensions. But if you want those, you're at the whims of the language. You can't just add them yourself.

I don't know how big of a deal this actually is. I've never used Clojure, and I've barely used other Lisps. Expressing what I want in Python is easy enough in practice (but maybe I just don't know any better?). But there's certainly something significant there that Python can't do.


I heard somebody make a similar argument one time in regards to Common Lisp. He said that with Java you would have to wait several years sometimes to get a feature (assuming the language developers actually cared about the feature), but with Lisp, you could just implement the feature yourself. I've always felt this is one of the strongest arguments for using a lisp-style language.

Edit: this is possible too in languages like Elixer and Ruby using macros, but eventually the readability suffers and debugging complex macros in those languages can be a nightmare.


I haven't done much metaprogramming stuff, is this just the nature of using macros or can it be solved with careful coding?


Macros are really powerful constructs that allows you to even change the syntax of the language, which means when they are used you can't trust your previous knowledge of how the language should behave. If you do it correctly you can make a Domain Specific Language that makes your code as readable as the documentation in domains the language was not designed to directly solve (or just generate code for you to avoid boilerplate that would be more unreadable by itself). If you do it incorrectly, you can end up with code you cannot trust or even understand (and language design is a skill that not everyone has in the first place).

About debugging, it is somewhat different from normal debugging since macros are a second layer of abstraction. You have a compile-time phase in which the macros are resolved (receiving code and returning code until there is no macro left) and a runtime in which the final generated code is run. If you only consider the runtime, then you're looking at code that is not the same as you wrote, which makes it indeed hard to see mistakes. But if you separate the concerns of compile and run time you can evaluate both separately most of the time.

Also, Ruby does not have macros. It has a very simple syntax in which everything is an object and all function calls are actually messages that can be interpreted in runtime which allows you to extend the syntax very easily by controlling the dispatch rules. What causes trouble is that classes are open and can be extended freely during runtime, so you cannot know that an object you're calling is not modified by any part of the code (including any library), a practice called monkey patching and usually considered bad practice since it makes code hard to reason about.


> get the AST (a data structure AFAIK), manipulate it in a sane fashion in Python, compile it, return the result. No string munching eval require

I use both Lisps and Python, and especially after learning to use Lisps I've wanted to do this in Python, so I looked for the ability to do what you describe here.

While it is technically possible to do this (like most things) in Python, it is quite the effort and nowhere near ergonomic. Among all the cases where I could really use this, I found it worth the effort to instead:

1. Manipulate & generate code statically (from ASTs), as a build-time / dev-time step, using redbaron etc.; or

2. Just use a Lisp (specifically, Hy).

Both methods involved writing quite a lot of code. In the former: to load, parse, match, and transform ASTs; in the latter: to port existing code (the code I originally wanted to manipulate) from Python to Hy.

----

The reason for all this is plainly the fact that Lisps are designed for this, while Python is not. Before learning Lisp, I used to wonder what could be so different in the language itself that could possibly make it that much easier. Now I use a Lisp-in-Python in production precisely because of this.


> While it is technically possible to do this (like most things) in Python, it is quite the effort and nowhere near ergonomic.

Indeed! Peter Norvig once made the same observation [1]:

> Python does have access to the abstract syntax tree of programs, but this is not for the faint of heart. On the plus side, the modules are easy to understand, and with five minutes and five lines of code I was able to get this:

    >>> parse("2 + 2")
    ['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison',
     ['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term', 
      ['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor',
       ['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]
> This was rather a disapointment to me. The Lisp parse of the equivalent expression is (+ 2 2). It seems that only a real expert would want to manipulate Python parse trees, whereas Lisp parse trees are simple for anyone to use.

The essential property of a Lisp, to me, is that the syntax is the AST. For any language which does not have this property, providing DOM-like access to the AST is neat but simply not practical for the user.

[1]: https://norvig.com/python-lisp.html


This is true, but I wanted to correct factual errors in the article.

If you or your team are Lisp users then Hy or Clojure would be good choices, but if you are just trying to create a transformation for existing Python codebases, the decorator approach is a good shoehorn.

On the other hand, you can also interpret the difficulty of AST transforms as a signal from the language designers (had van Rossum really never heard of Lisp?) that they hurt readability and their use should require a little ceremony.


I'm sorry, but I don't agree at all.

> if you are just trying to create a transformation for existing Python codebases, the decorator approach is a good shoehorn.

For transformations that need to modify the code, decorators are insufficient. Decorators have their value, and I have built good interfaces using decorators, which would have been rather difficult without them, but all of that was because decorators help _wrap_, not _transform_ code.

When I had a need to transform code, I looked for the right tool, and the takeaways of my searches are mentioned in my original comment.

> you can also interpret the difficulty of AST transforms as a signal from the language designers (had van Rossum really never heard of Lisp?) that they hurt readability and their use should require a little ceremony.

Nobody's claiming Guido had never heard of Lisp, or any non-lispy-language designer for that matter. Language designers make languages for varied reasons. Guido's reasons actually happen to be documented, and the only reason Python isn't homoiconic is because Guido didn't have a need for it, not because he explicitly wanted AST transforms to "require a little ceremony".

In fact, Python AST transforms do not merely "require a little ceremony". They are actually quite cumbersome. The standard library actually provides no help at all in transforms, making it necessary for libraries like Rope, RedBaron, and Astor to be written.


Thank you, interesting comments, especially switching over to the Hy language for production. I got very excited about Hy, then an update took away a built in “let” and that turned me off a bit. Are there any good directions for patching in the old code supporting “let”?


TBF I don't know how common it is as Python's AST API is pretty gnarly (not to mention really hard to work with / debug as it will usually segfault if you look at it funny). And IIRC the error messages coming from there are significantly worse than those coming from source.

Third-party packages do make that somewhat better, but still not great. IIRC Jinja compiles templates to Python source code then compiles that to bytecode using the relevant builtins because it's simpler & more reliable than doing straight AST manipulations.


Python is not as flexible as Lisp in a manner of AST manipulation


Python's approach seems to be: expose functionality like the AST for the 31337 ninjas that really want to go there, while maintaining intense focus on producing a KISS tool for the other 95% of its audience.


A lot of (most?) of the benefit of macros comes not from writing them yourself, but from using macros that others have written. Even if only 5% of the people are writing macros, that 5% likely includes many library authors. Making it harder for them impacts their downstream users too, even if those people aren't actually writing macros.

IMO the macros in Clojure make it easier for library authors to produce KISS, high-level interfaces for the rest of us.


This ultimately impacts code quality negatively. Macros aren't a tool "for the 31337 ninjas", not more than class inheritance or generic types, or hell, even procedures. Macros solve a particular class of problems - they eliminate bloat and boilerplate, allowing you to express your code closer to the way you think about it.

Much like classes can be used as data transfer objects or to build out a large inheritance tree, macros can be used tactically - to DRY up things you otherwise couldn't, or introduce some readability improvement - or strategically, to lift the code up another layer of abstraction, or create DSLs that let you write your code much closer to the problem domain. Because Lisp supports them in an ergonomic way, macros are not seen as something special, but just another tool in the toolbox.


Python has become one of the most popular languages. I think they're just using it as an example of "here's a language you probably know which doesn't support this" -- or, as you note, kind of supports it now but makes it a real pain because it's not native to the syntax.


You might enjoy this talk- Live Coding a Compiler using Clojure at https://youtu.be/_lMltUNB6Yw?list=PLhYmIiHOMWoEgJEvgkmUe8D0a...


It's more that code and data have the same representation. For example, where is the Json of Python? Where is the Python object notation which is both how you represent Python Objects in code as well as when serialized to data over a wire?

[1 2 3] is both the serialized form of a vector as well as valid code to create a vector of itself.

Now in JS, only object data is homoiconic. In Lisps, even code expressions are homoiconic, thus (+ 1 2) is the serialized form of the list of function + and args 1 and 2, as well as valid code to add 1 and 2 together.

You do not need macros for homoiconicity. Macros is not what makes a language homoiconic. But once your language is homoiconic, it is very easy to add macros to it, and macros become much simpler to write. Which is why Lisps have macros as well.


> I'm all for Lisp but this just isn't convincing

Nothing will convince you until you give it a heartfelt try. The joy from using Clojure is a real thing. Unfortunately, there are not too many languages out there you can say the same thing about (including Python). Clojure is not absolutely frustration-free PL, but it maintains a good balance between "fun" and "profit".


A python decorator is just a syntactic sugar for :

f = decorator(f)

Afaik, the decorator deals with the internal function as a black box.

But with lisp being a lisp, the macro writer can choose the level of blackboxiness they want to deal with.

Note: I love both Python and Clojure and use them extensively.


As others have hinted at, higher-order functions like decorators can inspect and manipulate the internals of a "black box" function object with the ast module[1]. This would be comparable to a lisp function taking a quoted form, expanding and evaling it at runtime as an ad-hoc macro funcitonality as is common in more bare-bones lisp implementations. Aside from the ease of manipulating quoted forms (i.e. asts) in Lisp and Clojure, a big part of the power of defmacro and kin is that Lisps distinguish (depending on implementation) between read-time, macroexpand-time, compile time, and eval time, giving the macro author control over when and where the macro is executed as well as how. As an example, this makes things like lazy evaluation (without requiring users to explicitly "thunk" values into functions) trivial to implement in lisp

[1] https://docs.python.org/3/library/ast.html


Thank you, that was very informative. I do remember that Python had the ast module for a rules engine implementation, but I felt it was too cumbersome..

Thank you.


Python's biggest problem wrt functional programming: multiline inline lambdas are not possible.


> get the AST (a data structure AFAIK)

how?


    ast.parse(inspect.getsource(func))
doesn't work in the python shell though.

You could also decompile the function's code object.



I love the idea of Clojure.

I find the ecosystem immature and full of ideas about "libraries, not frameworks" that leave developers incredibly vulnerable to their own ignorance and second-order ignorance. The number of developers I know - or even know of - that can be trusted to develop a useful and secure web application from the ground up with this kind of tooling approaches zero.

Clojure is a cool language. I can only hope the ecosystem soon matures enough to make it one that's suitable for serious use.


Which libraries are people expected to plug together the security parts? I'm not pushing for clojure but a lot of other languages and frameworks are taking this approach too including Node and Go.

Wouldn't the security stuff be a part of the individual packages? Ie authentication package handles sessions and tokens, an http server/middleware with built in csp and ssl, orm, the data stuff handling serialization and data parsing?

Technically rails is composed of a bunch of smaller gems with a similar responsibility tree although without the centralized CVE distribution and pressure that creates.

When I used Clojure it was always mini frameworks which created a base set of important packages with a pluggable middleware system on top and flexible data back ends.

The individual packages need to have domain experts for their particular attack surfaces and exposures.

If anything its about a lack of eyes or community size on the various wholes, rather than expecting the end user to do the individual hardening or plumbing.


You're right. Many things can be handled by packages, abstracting them away from the developer.

Of course, that's assuming the developer thinks far enough ahead to remember that they need authentication, decide how they want to handle it, figure out what they want for a CSP, and so on. Other things cannot be so readily handled. Authorization, for example, isn't a concern that can be bundled into a pre-fab function. It's something application-specific that has to be re-evaluated with every single web request and is going to be subject to unique business requirements.

Frameworks tend to handle these issues by setting out a set of conventions to adhere to, within which many of these concerns are handled for you to the extent possible. It's far from perfect, but it does a lot to reduce the chances of someone forgetting (for example) that forms need anti-CSRF tokens because their form-generation function doesn't do it for them or automatically work with the form-accepting and validating functions.

Or, to use example I've seen, the logging package's ignorance of what might be sensitive in the strings its been handed turns into secrets being sent to the centralized logging system and exposed to far more people than they should be. The developer didn't think about what might be in what they were logging, the package assumed that the developer did, and the result could have been avoided by a more integrated whole.

A culture of libraries instead of frameworks can mean that each project may have to make all the decisions a framework does all over again. Is everyone going to get it right every time?


Several purported cons of clojure according to this conversation, including yours, are more a critique of using a niche language with a small community than really about the language. Addressing that explicitly can clarify the debate. Also, clojure is a special beast in the sense that can leverage a big community easily (java). This is not covered in your web framework example, but you can use clojure in frameworks (e.g. you use unity through arcadia, as a framework)


> The developer didn't think about what might be in what they were logging, the package assumed that the developer did, and the result could have been avoided by a more integrated whole

Umm, is there any all in one web framework that protects against this to any level of guarantee?


It's been my experience that Rails logging can and will do things like censor password fields when passed objects.

So, yes, it is possible for logging (and serialization) systems to defend against this kind of thing.


In my experience these password logging events tend to be at a lower level than object serialization. They're usually at the raw request level.


The situation in Node and Go is better by the fact that these ecosystems have extensive documentation related to web security.


That's the many eyes thing but I don't see how it's unique to Clojure's culture which was my point.

Node only got to that point through plenty of failure and last time I used Go it didn't seem much different than Clojure.

The only reason I'm not using Clojure is the lack of types but that's another issue.


> The only reason I'm not using Clojure is the lack of types

Have you tried Clojure.spec? Have you tried generative testing where you generate data based on given Spec? Have you tried using Specs for form validation? Have you seen how Specs can be shared between Clojure and Clojurescript? Basically between two completely different platforms. Even vanilla nodejs cannot do that, even though the code is running on the same [JS] platform.


That's just a misconception imo. Clojure is full of frameworks. The real issue is overwhelming choice and the no handholding make your own decisions approach. Probably accentuated with a lack of beginner guides.

Have a look at Pedestal, Yada, Duct, Hoplon, Catacumba, Coast, and Fulcro.

Those are all actively maintained frameworks being used by different people in production.

On top of those, you have the meta-libs frameworks such as Luminus and Chestnuts.

And what most people don't know is a lot of Clojure devs use a Java framework, because those are also available to be used from Clojure. I have a Spring MVC Clojure app for example. Lots of folks use Vert.x for example.

So whatever your definition of mature is, Clojure has everything you need and more. If you mean mature as in, one and only one way which everyone is rallied behind and all guides and tutorials teach and use, then no.


There was a interesting and relevant post about this recently from a pretty established clojurist:

http://flyingmachinestudios.com/programming/why-programmers-...


there is luminus that assembles all the parts for you.


Which would be wonderful! Except for the responses of visceral disgust I get from Clojure developers when I suggest using a framework. The culture seems hostile to actually using such things.


> While the primary platform is the JVM, superbly uncool but stable and relatively performant

My impression was that java has the reputation of being "uncool" but JVM is highly regarded as modern marvel.

I am wrong about this ?


No you aren't wrong. Java the language has a reputation of being verbose, boilerplate-heavy, and inspiring over-engineered OO cruft. JVM on the other hand is a pretty modern and performant and nice-to-debug platform.


It just depends who you ask of course, but that is the idea you get. The JVM is pretty cool tech with many languages running on it that are considered cooler than Java.


IT Java is uncool, but then IT is uncool even if written in Clojure (though the psychological aspect of using a "cool" language will likely dampen the existential pain of writing yet another book keeping CRUD app since now you are using s-expressions!)

In my experience, people who say silly things about Java -- the technology, which certainly includes the Java Language Semantics, and JVM -- actually do not know what you can do with Java (the language).

So yes, you Lisp nerds have you homoiconic 'mash' of syntax and thus AST manipulation, and we have first class Class Loaders and Byte Code Engineering and the sky is the limit there. Magic.

But the sad reality of programmers' lives is the disconnect between the required cognitive capabilities of workers and the actual (domain) task at hand.

tldr;/ its your job that is uncool not the tech.


> But the sad reality of programmers' lives is the disconnect between the required cognitive capabilities of workers and the actual (domain) task at hand.

Yeah, I just don't understand the less lines of code argument. This is not the limiting factor in software development, it's just not. The limiting factor is having enough information for the immediate Use Case and enough business domain knowledge to come up with a solution that delivers value to the business. The time spent on delivering that value dwarfs the time it takes to write some extra code. Let's look at Uncle Bob's example in the linked article...

(println (take 25 (map #(* % %) (range))))

While fancy, that code is very Perlish and I think we all know the common criticisms of Perl. Also, fancy doesn't pay.

What I've found to have a much larger negative impact on a project than the language is overly complicated architectures. To the extent Clojure can help with that I'm all for it. But from what I've seen, Java 8 with some clear conventions and constraining source code to the implementation of the Function interface can get you a lot of the same benefits.


> though the psychological aspect of using a "cool" language will likely dampen the existential pain of writing yet another book keeping CRUD app since now you are using s-expressions!)

Damn! I felt that.


My experience is that Java programs tend to be buggy, slow messes. A lot of it is probably due to the inexperience of the average Java developer, but how do you explain Eclipse ?!?


My impression is that this perception is because of the slow garbage collection on JVM which eats up lots of cycles. Also all JVM languages tend to consume a lot of memory.


Clojure Help:

This is super apropos since I am JUST learning Clojure via the Clojure Koans.

Any tips from clojurists on getting the repl (and readline) working fluently? I'm using Leiningen but it's not responding as I'd expect. I would think this would all set up more effortlessly.

Tips on getting a new Clojure setup working would be appreciated.


Join the beginners channel in Clojurians slack, there are many patient people there volunteering to help with things like this.

Try out Calva for VS code, read the official REPL guide here https://clojure.org/guides/repl/introduction


> Any tips from clojurists on getting the repl (and readline) working fluently? I'm using Leiningen but it's not responding as I'd expect. I would think this would all set up more effortlessly.

For me, Boot[1] has completely replaced Leiningen, and I wish it was used by all new Clojure projects, when deps.edn[2] is not enough.

[1] https://boot-clj.com/

[2] https://clojure.org/guides/deps_and_cli


Thanks for all the tips. I started the book Brave, installed Cava and got it working, and am considering finally biting the bullet on emacs. Two birds with one stone!

Thanks for the tips!


If you're willing to pay a monthly fee, you might like this REPL driven development course:

https://purelyfunctional.tv/courses/repl-driven-development-...

EDIT: and you might also want to check out https://clojureverse.org/


You can ask questions at: ask.clojure.org

And like others said, people on slack are super helpful, don't be shy, the community is great.

That said, Clojure does not invest a lot into beginners. Not a lot of tutorials, guides and tooling are similarly unfriendly and assuming you know a lot. Not trying to scare you off, just setting the right expectations.


I recommend emacs, try braveclojure.com - it's a pain to set up properly but well worth it.


As an avid Emacsen, I would usually recommend Clojure-mode and CIDER only if you are already familiar with Emacs. Learning both - Clojure and Emacs can be really frustrating and may potentially become a "deal-breaking" impediment. Almost every [popular] editor/IDE today supports Clojure (not many mainstream langs can proudly say so)


I use Leiningen as well and I have no issues with it. REPL startup is a bit slow, as is expected from most JVM programs but a bit more so for Leiningen.


You can start the JVM in well under a second, the problem is most JVM programs don't care about startup time thus startup becomes slow. Most JVM apps are designed to be long lived.


Chapter 2 of Clojure for the Brave and True will get you an Emacs environment set up with minimal effort!


I just went through this chapter. Some of the packages are out of date and the environment does not work correctly. I'm still trying to figure out how to get emacs to connect to the repl.


Indeed not so minimal effort, I had to skip that chapter. Hopefully it will be updated in the future.

I've been using Atom so far, and I will try Cursive later


Hmmm, interesting. Set mine up no problem on a Mac about 9 months ago, but I suppose I might have gotten lucky.


try to get integration with repl working with your IDE.

Not sure what you are using but i use CIDR on emacs.


One (stupid?) thing that annoys me about closure is that native clojure libraries use snake case while java libraries use camel case. You could obviously write a new defn macro to take of all this and make it consistent, but it might have just been a better decision to just make everything camel case, in in Lisp-like fashion.

Also I find Clojure a little dogmatic in regards to mutation, more so than even Scheme. I find Racket to be better designed and more elegant, though currently the performance isn’t as good.


The macro solution is only solving for the aesthetics of the code and is opening the door to infinite edge cases where it wouldn’t work or could have unintended side effects.

I don’t disagree with you that it’s ugly and sometimes tedious to use Java in Clojure... but that is one of the foundational strengths of the language. It’s also something that you’re hopefully not doing a lot of... so I think the trade off of seeing ugly Java syntax sprinkled in your project is worthwhile.

You’re standing on the shoulders of giants with zero to no penalty as far as interop. I might reframe your mentality to see this as a huge benefit and not something stupid!


>Also I find Clojure a little dogmatic in regards to mutation

I don't think it's dogmatic (which I would say has a negative connotation) but simply opinionated because fundamentally at the heart of clojure seems to be a concurrency model that heavily relies on immutability to make sense, so it's a logical consequence of that design choice.

And I think it's an appropriate one because the mutable thread and lock world that we get in mutable imperative languages is honestly a little bit insane.


I find snake case much less visually noisy. SomeVarLikeThis requires more visual parsing effort for me than some_var_like_this. And when I have used Clojure, I would say less than 1% of my code was doing Java calls - so the occasional CamelCase would stand out visually, helping me notice them.


Clojure being dogmatic is the reason I love it.


Agreed. While I don’t always agree with its dogma, without it I don’t think I’d still be using Clojure, yet here I am ten years after first learning it. Clojure is great because it takes a strong stance on how certain things should be done.


yep. also, i think learning how clojure's dogmas synergize with each other is the determining factor in whether one is a clojurist for life or not.


> While OO seems to be out, and FP the new hotness

Funny that. While I prefer FP, and did since I've learned Python, my teachers are really hot about OO, even pushing us to use it in Python projects.

(Looking at projects from previous years, they mostly seemed to use Java. Then also because of browser integration and obviously because of the OO <-> UML <-> Project links ?)


Is there any reason to believe FP is the "new hotness". Each year in the Stack overflow language trends report the FP languages are barely making an impact.


FP languages are not that big. FP architecture and practices are very popular. React, Immutability and more have become mainstream.

A recent example is SwiftUI. While Swift is not a FP language, SwiftUI (FP UI framework) and Combine (a FP toolkit) is being pushed by Apple as the future UI architecture for all its OSes.


It's the new hotness if you measure hotness by the amount of Medium posts written about it.


I don't know if you have noticed, but FP is not "new" anymore. Almost every programming language today either has some FP-flavored features or keeps adding them. And there are plenty of good reasons why many [smart] programmers think that OOP has failed.

Complex static type systems on the other hand - are kinda "new kid on the block". And if we are not careful that may become a "new OOP" - doctrine so complex and convoluted that could become an impediment in building software. I know, static type systems are cool and all but it's easy to get from something elegant and simple like Elm into say Haskell, Idris, Agda etc. and into the weeds of homotopy type theory.


Need someone to explain why I should use clojure over scala because typelevel seems pretty well developed


Clojure is concise, expressive, not noisy, opinionated (with generally appreciated opinions), dynamically typed, a Lisp, functional with some escape hatches (but not confused with being FP and OOP), and built to solve real problems. Scala is either the opposite of these, or at least not as much of these.

IMO, less code is better. Clojure is about as terse as you can get and still be readable (by my opinion). Scala is very visually noisy in comparison. While you can do types with Clojure.spec, you get to choose when and use when useful to you.

Rich Hickey is the best communicator of why Clojure is good. Here's a relevant starting point a video talk he gave on the 10 year anniversary of Clojure: https://youtu.be/2V1FtfBDsLU?t=1636

All this said, Scala has more jobs than Clojure in the same way that Java has more jobs than Scala does. It's more Java-like than Clojure (and not just because it isn't Lisp). (And incidentally, only people who haven't tried at all to use Clojure complain about parens ().)

Sadly, I think Javascript is the long-term winner. Python is also a winner, but it doesn't have the front-end reach that JS has (while JS also has server side). Clojure and Clojurescript can do both, and are an awesome choice if you don't mind working alone and having few job opportunities. However, if you want to focus on one language and be able to do a lot of things, and have lots of job opportunities. Javascript is actually the one I would suggest.


Though Clojure jobs tend to be the highest paying.

https://insights.stackoverflow.com/survey/2019#top-paying-te...


It's pretty common for niche languages. The fewer developers there are, the higher they are likely to be paid.

These jobs are pretty much impossible to get, though.


I suspect this is because Clojure developers tend to be more senior.


Not going into this explanation (as I believe the qualities of languages can only be assessed through experience, not theoretical arguments), but I, and many others, have been through Scala with high hopes before finally settling for Clojure - this should tell you that there are indeed good reasons for favouring it.

An analogy to pique your curiosity: I would say that if Java was a balloon, Scala would be a zeppelin, and Clojure would be a plane.

I would also say that the stewardship of Clojure is much saner than that of Scala, due to a strong emphasis on stability. Typesafe / Lightbend is bigger than Cognitect, but big does not mean reliable. One sign of this is that Clojure now grows via libraries, the core having reached stability. Scala is still figuring out its fundamental semantics.


I feel I see Scala brought up in response to almost every Clojure post, but I can't tell why. They seem like polar opposites. Clojure has a simple syntax and dynamic typing and no classes, while Scala has a complex syntax and classes and static typing with inference. Why would someone looking for one be interested in the other?

Is it just because they both run on the JVM? For all my Clojure projects, I picked the language despite that, not because of it. It's merely an implementation detail.


Clojure: live repl into running code

Scala:

Clojure: Explore the problem you are facing with quick prototyping in the repl and see the blind spots, the unknowns, the shape of data received from flaky integrations. Mold and shape your functions as the problem becomes clearer.

Scala: Best it can do is write unit tests and the debugger to look at what exactly is happening, devise a bunch of classes to handle them, rinse repeat and of course wait hours for recompiles. Refactor when new unknowns surface and curse at the fucking compiler not knowing what type it should be inferring.

Clojure programmers are done before Scala has a test environment up.


> Explore the problem you are facing with quick prototyping in the repl and see the blind spots, the unknowns, the shape of data received from flaky integrations. Mold and shape your functions as the problem becomes clearer.

This is a great way to end up with undocumented code with no tests. The repl definitely encourages this process, but you should be doing this via quick running unit tests rather than the repl so that when you are done you already have a set of tests. Otherwise tests are an after thought and usually after thoughts are either not done or done poorly.

edit: you can always run your unit tests via the repl too, if you'd rather not use a fast test runner.


I'm sure you're happy with Clojure and I'm not going to try and convince you Scala is better but you have some misconceptions.

Firstly Scala does have a couple of REPL's, the best being Ammonite which is mentioned below. It can be embedded in code to provide a live repl just as your Clojure program. I often work in Scala in a similar way to I work in Clojure; creating and testing new types and functions in the repl before moving them back to a source file.

The way you build programs sounds quite horrible, but I know people do it that way and it works well enough. Your complaints about long compile times are largely irrelevant. Incremental compilation means it's usually 2-3 seconds at the most before I can test a change. We don't build programs as a bunch of classes by the way, that would be OOP. Scala is more commonly written in a functional and/or streaming/reactive stle. Also I'm glad you brought up refactoring. It's a breeze in Scala because of the strong type system. Poorly defined data can be expressed just well in Scala with types as it can in your blob of maps of maps, and it's much easier to work with.

Finally you may finish your program first; it appears to do the thing you want but you can't really be sure. There will be more runtime errors such as NPE's and type errors because you don't have mechanisms to remove them from possibility. I'd much rather maintain and evolve a Scala program.


> Scala is more commonly written in a functional and/or streaming/reactive stle.

This is the exact opposite of my experience. All the Scala I've seen "in the wild" is basically Java with a slightly different syntax. All OO, with the odd high-level function thrown in to prove something I guess.


Scala also has a live repl.


Please provide link as I need one.

Ammonite is not it though.


I use jupyter notebook for Scala. I like having the same repl software for multiple languages.

The kernel I use (almond) is based on Ammonite, but I believe there are others out there.


Please don't try to compare Clojure REPL (or any Lisp REPL) with jupyter notebooks or something similar. The latter are mere "interactive environments" compared to "real" Lisp REPL. You have to actually try Clojure REPL and see what makes it so special.


Thank you for the jupyter notebook tip though. As I do have a biggish Scala app to maintain any help with it is good. :) Thanks again.


Speaking as a professional Scala developer I can’t explain why you’d use one over the other. They are very different yet I know people that successfully develop and ship software using one or the other. I think it comes down to personal preference


My personal reasons are:

  1) Clojure is more fun, more interactive and interesting. 
  2) You learn a lot more, more quickly. In Scala it's easy to just fallback to writing classic OOP with a nicer syntax. Clojure forces you to learn FP, meta, logic, etc. much sooner.
  3) I'm more productive in it.
  4) The interop is way better.
  5) Compile times are instantaneous. 
  6) The community is friendlier.
  7) New versions are always backwards compatible, Scala often breaks compatibility and updating is annoying.


as i was talking to a friend about clojure i came up with an analogy i cant shake: clojure is what happens when you just make your programmer directly write an AST. a bit reductionist, but anyone care to debate this?


Isn't that the point of lisp and homoiconicity in general? That you're writing an AST that you can also treat as a data structure?


ok so... why is writing an AST more ergonomic than having some syntax?


It’s really not. That’s part of the problem with lisp. But you can treat your code as a data structure which offers a lot of benefits. For instance, it’s basically impossible to offer control flow as a library in normal languages. That is, if you wanted to implement an unless statement like in Ruby, you’d have to use thunks or some other form of lazy evaluation. Macros allow you to just transform the unless statement into an if statement quite easily by just operating on the AST. Of course, you can do this without homoiconicity but it’s a lot less ergonomic.

Also part of the reason Babel exists is to take the place of a macro system. Instead of transpiling array spread into a function polyfill, macros would just output the pollyfill into the AST. What’s nice about this is that since it’s userland, people can test drive new features. Rust did that for async/await. And the pattern matching proposal in JS is based off a SweetJS macro.

(Obviously not all language features can be implemented as macros but you get my point, right)


i think so. thanks for the explainer!


That’s a fair description of the syntax, but Clojure is more than just syntax.


whats the key piece missing for you then? because my friend was definitely very keen on the "no syntax" bit


REPL. homoiconicity makes "real" REPL possible. Anyone who hasn't tried Lisp would say: "a lot of other [non-lispy] languages have REPLs". But only after trying Lisp REPL you can truly experience why is it so special. It is extremely liberating to be able to evaluate any part of your [running] program, anytime, [almost] without any preceding ceremony. Even famous Jupyter Notebooks fall short in comparison.


> It is extremely liberating to be able to evaluate any part of your [running] program, anytime, [almost] without any preceding ceremony.

Sounds like Smalltalk.


I tried to learn (get to know) Clojure by writing a library for cacheing (memoizing) function calls. I thought it would be a simple exercise, but it turned out to be not so easy at all, with lots of special cases.


Clojure's implementation of `memoize` is here:

https://github.com/clojure/clojure/blob/master/src/clj/cloju...

What were the special cases for your use case?


Can you give more details? Shouldn’t that be a matter of a macro redifining defn (o create a defn variant a la schema’s defn-spec, or tufte’s defnp ) to capture the call?


That seems like an unusual first project. Any reason you chose it? Were you more interested in the language semantics?


It was because (1) a project I was thinking of would lean heavily on memoized calls, and (2) it would allow me to learn about less basic features like introspection and (3) to quickly see how cleanly the language was designed to not be disappointed later on.


You could check out Clojure's built-in memoize (https://clojuredocs.org/clojure.core/memoize)


It is a good language. Python is easier for quick code.


I disagree. It's the matter of familiarity. Clojure is extremely productive and the best PL I have tried (out of many) to quickly build prototypes.

Whenever I have a requirement to solve a problem in a different language (e.g. interview challenge, etc.) I would still do it first in Clojure and then convert it to whatever language is required. Yes, I have to do the same work twice, but even then - I am still faster doing it that way.


I'll take simple over easy ;-)


> Clojure is explicitly designed as a hosted language, which I think was a very good move.

I’m not personally a fan of a VM such as JVM. Sure you get some easy benefits, but now you have two things that need tuning. Two things that need periodic updating.

Personally the total encapsulation of first-class static linking (such as Golang) is my preferred future. It must be first class and baked into the language in its design.


You're missing the point. Because Clojure is hosted you basically can share code between JVM and Javascript. It is extremely nice when you actually can do that. Even in NodeJS, essentially running on different implementations of the same platform - that is hard. As it turns out - not so hard in Clojure.

Now, imagine also having access to thousand of libraries written for JVM and Nodejs and Browsers? There are attempts to port Clojure to other ecosystems, making it possible to use them there. What if someday you can write Clojure code that can be shared between say Rust runtime, NodeJS and a browser? Wouldn't be that cool?


Clojure/Lisp is super nice until you have a bunch of transformations to make then you've to keep everything in mind without having a named reference in front of you (unless you omit the threading operators and abuse the let statement). It becomes exhausting after a while and makes you question if you are working for the machine or the machine is working for you.

Yes, it's more succinct and elegant but is it worth it?


You are absolutely free to decompose computations into more `defn`s, `let` statements, or threading operators. Or mix and match those.


You are 100% right but I am not writing all the code. My job is mostly maintaining other developers' code. And turns out, languages can have great effect on making not-so-good developer write reasonably good code (Go, for example).


One thing I've come to realize is that it is not enough to write code that's merely compilable. That's a low bar. A team needs a consistent style and that has to be enforced automatically. Automatic formatting is a good first step but that's also not enough. Say you don't want too many nested s-expressions. Then write a tool to reject code that has too many of them. Run the tool together with the compiler. Prevent merging if the tool complains unless a special exception is noted.

Tooling is important.


Likewise my Clojure jobs tend to involve reviewing multiple devs' code.

It's certainly possible to converge towards some shared notion of what constitutes maintainable code.

We additionally have an internal styleguide plus some formatter-related tooling.


It's really your choice whether or not you want to name intermediate expressions. Just use plenty of `let` if you are concerned.


Would Specter help here?


Not familiar with it. I will check it out. Thanks for the recommendation.




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

Search: