44 comments

[ 3.7 ms ] story [ 37.8 ms ] thread
Just FYI to the author, I can't read this on Firefox mobile, the text is clipped weirdly. Reader mode does work however.
Thanks for the report, will try to fix this.
Since you're here, I'd just like to say that the font you're using (Bona Nova) is wonderful. I smiled ear to ear when I saw the capital-Q in the phrase "Quite possibly".
I'm so happy that you appreciate it!

This is typographic patriotism on my part (I'm Polish). This typeface [1] is a design of the late Andrzej Heidrich, who also designed the banknotes of the Polish zloty.

One of my two favourite Polish typefaces. The other is Antykwa Toruńska (Torunian Antiqua) [2], a 1960 design by Zygfryd Gardzielewski.

[1]: http://bonanova.wtf/?lang=en

[2]: https://jmn.pl/pliki/AntykwaTorunska-doc-en.pdf

I just find amazing that the project is working with clojure 1.2.0 as dependency,

Which is about 8 years old.

Clojure hold a soft spot in my heart for emotional reason, but the engineering behind it is top notch.

If any of you need motivation to go and learn it! Please do it! You will never regret invest time in clojure.

Hear hear!

I've written about this (free online) book here on HN before but it really gave me the kick I needed (and lightweight humor) to actually start using Clojure(Script) professionally, so if you're on the edge, give "Clojure for the Brave and True" a try!

https://www.braveclojure.com/clojure-for-the-brave-and-true/

> The lessons are carefully thought out and unique. From learning asynchronicity to the lyrics of Lady Gaga, to learning about thread lock in the context of drunken dwarves, every chapter is fresh and (in all honesty) beautiful. - Artur Malabarba, CIDER maintainer

This is one of the few freely available online books I liked so much I actually purchased a print copy, even though I mostly used the online version while reading through it :)
And you are not alone. I admit I did the same, exactly because it was so good and I wanted to give something back.
Great book. I loved it as my first clojure book! Thank you. Definitely would recommend to people wanting to learn clojure.
If any of you need motivation to go and learn it! Please do it! You will never regret invest time in clojure.

Agree that Clojure is a good language to learn. If you plan to use it beyond educational purposes though, expect to hit many walls when/if you plan to write real world apps with it. The foundations are there but the ecosystem isn't. Someone will mention the Java libraries, but at that point you are not really writing Clojure. Also, be prepared to deal with the cultish obsession of the community. They have very strong opinions but will never elaborate.

If I'm working with Java libraries, I'd much rather do it from Clojure. You do end up having to write something like an interface for any library you're using--but to be honest, you tend to do that in Java anyway.
In some cases, depending on the library, writing a small Java class makes the interfacing easier.
Libpython.clj allows python interop. So you have the entire ecosystem of python and Java at your hands. I would argue that this should suffice to go far beyond educational purposes.
>Someone will mention the Java libraries, but at that point you are not really writing Clojure.

Uh, Clojure was literally written from day one on the founding principle of Java interop? Using Java libraries from a functional lisp is literally the reason Clojure exists.

> Also, be prepared to deal with the cultish obsession of the community. They have very strong opinions but will never elaborate.

I'm curious to know what "cultish obsession" you are referring to. Also, what strong opinions have you been unable to get elaboration on?

When working with Clojure teams, I personally ran into "libraries, not frameworks!". Interesting maxim, but I never found any kind of satisfactory explanation. There was a general deep-seated belief that Clojure was the right solution for every problem, with a similar amount of elaboration.
I have that maxim for...basically every language I use, though, despite never using Clojure.

There's a little wiggle room; I think it's largely dependent on what you define each by, but basically anything that forces me to write my app in a certain way, and limits how easily I can slip in things that are clearly libraries, become liabilities, and I would prefer to avoid.

Obviously that's hand-wavey; there are plenty of things that people will call frameworks that nevertheless dictate very little, if anything, about the structure of your code, and which are easy to get to play nicely with other libraries. But there are also plenty that...don't. But to play it safe, I look at things that described to me as frameworks with far more suspicion than I do things described as libraries.

It's definitely a wonderful goal to aspire to, and ideal for small or personal projects. In my experience, I've found this particular approach to be, at times, of unfortunately limited value in developing and maintaining enterprise systems.

A previous employer of mine was a Clojure-heavy shop. There were multiple teams. A Clojure dev going from one team to another could expect to find themselves in a very different stack with very different conventions to do very similar-looking web service tasks. In essence, each team had composed their own under-documented framework.

I've had no issues with it in enterprise systems.

Note that it doesn't mean you can't use a framework...it just means prioritizing ones that feel more like libraries, and minimize their surface area.

I've worked in a shop that had 8 dev teams, all working on different systems, all in Node, using Express. We favored standardizing, but standardizing on something easily extensible. The middleware approach made including non-standard things really easy; if I needed to include some custom authentication library that wasn't written for Express, it was trivial to do it; a couple lines of glue was all it would take, even if it included some initialization.

Compare that to a Spring, say. I have to know about the framework's authentication interfaces, implement a comparatively large amount of glue in terms of those interfaces and configuration, and even more hoops to jump through if there's state I need to initialize first for the underlying library.

That's what I mean. I'm not arguing for or against standardization, but extensibility and minimal cognitive overhead in extending it.

You're absolutely right. Extensibility and minimizing cognitive overhead are imperative.

What I found through my experience is that offering many hooks for extensibility sometimes increased cognitive overhead for the team. With so many ways to accomplish a given task, no agreement on how to do it, and no guiding conventions, moving from one part of a repo to another can entail nontrivial cognitive overhead. Moving to a different project with a different collection of extensibility approaches employed for the minimal cognitive overhead of those authors sometimes compounds the problems. I've seen developers do things that were easy, obvious, and extensible to them but utterly incomprehensible for everyone else. Having the freedom to compose libraries as they saw fit may have made it more likely.

Compare to Rails. For all its many, many warts and often exceptionally opinionated quirks, it's often relatively easy to move a junior or mid-level dev from one part of the codebase to another. Or one team to another. Or even one company to another. Many of the conventions are the same and the standards largely match, making onboarding easier. This can also make it easier for a random dev to predict how the system might be extended for a given task, easing maintenance over time.

I think the core tradeoff here is between what minimizes overhead and cognitive costs for an individual and what is optimal for an organization. Also, I ran into issues with Clojure teams composing their own stacks and forgetting things like basic security measures of the sort that mature, pre-fab frameworks like Rails build in by default. They minimized the surface area so hard that they maximized the ignorance they exposed, producing services that couldn't be bothered use CSRF tokens because the developers had never heard of them.

Again, I think you're right. It's about extensibility and cognitive overhead. I've just found there to be some nuance to what approach provides the best outcomes. I don't particularly enjoy being bossed around by a bucket of bits that tells me how I'm allowed to express my wishes, but I've found that my emotional experience of it isn't the important thing.

Clojure is a good solution for many problems (obviously not all) mostly because it reaches many capable runtimes, java, js, python and interop is first class

What you might be encountering is after a few years of Clojure becomes such that you'd rather not be writing anything else and that is semi-practical as opposed to many other niche specialised languages where it's not so realistic

Libraries, not frameworks is a point of contention even within the Clojure community, libraries tend to compose and upgrade better over time but there are also frameworks in Clojure

Luckily we don't have one that dominates the language so totally that it cannot be ignored despite what inevitable deficiencies it has

Keep in mind most Clojure developers came from other languages and have Vietnam flash-backs to many frameworks

At this point I have Vietnam flash-backs to dealing with the hand-rolled, ad-hoc, half-built, undocumented frameworks that Clojure teams I worked with produced. Seems like a weird form of therapy to me, but what do I know?

I also found the points about interop to be nearly academic. The teams I worked with went to significant amounts of trouble to avoid using it whenever they could.

interop is first class

That's hardly true, not even clojure.org claims that. Interop is far from seamless.

My reference point for my comment is ReasonML which on its homepage claims "Easy JavaScript interop" In comparison, I didn't find that to be the case
I would say that "first class" is a perfectly reasonable way to phrase it. While clojure.org doesn't use that exact phrase, host interop has always been a central pillar of the language:

>Clojure is designed to be a hosted language, sharing the JVM type system, GC, threads etc. All functions are compiled to JVM bytecode. Clojure is a great Java library consumer, offering the dot-target-member notation for calls to Java. Clojure supports the dynamic implementation of Java interfaces and classes.

What do you mean by "far from seamless"?

(comment deleted)
> libraries, not frameworks

I think this originated in a conj talk several years ago. However, the speaker mentioned that it was simply too early to have Clojure frameworks, but that in a few years they will be helpful. Can't find the talk at the moment unfortunately, may have been by Alex Miller maybe in 2011 - 2013.

(comment deleted)
> "libraries, not frameworks!". Interesting maxim, but I never found any kind of satisfactory explanation.

I mean, that's more widespread than just Clojure; it's a generally-held belief in most of the functional programming world.

A library is just a bag of functions you can reuse at your leisure, hooking them up as you wish, calling the ones you like, passing references to the ones you like, and replacing or dropping the rest. It's a box of LEGOs. You can mix it into a big box with your own bricks and other libraries' bricks, passing the closure from X to the HOF from Y, and it all just works together, because nothing's expecting anything more specific than "data of the right type" or "a function of the right type", where the types are all basic core-language data types.

A framework already plugs some parts together for you, in a way that means you can't just replace some of those components with something better or more suited to your use-case. It's more like a molded plastic figurine that happens to have some explicit LEGO attachment points on it.

I personally know which one I'd prefer to find, upon opening up the docs for a new library. Every framework I've ever used, I've eventually had to fork in order to have an app inject some bit of customization into its behavior in a place it wasn't expecting such customization would be needed. Libraries don't have this problem.

Libraries-over-frameworks is also a bit of old-school Lisp-ism poking through. Regular programming languages tend to do things like web apps or games in terms of an opaque framework driver piece, that gets configuration handed to it in some descriptive format. To someone coming from a Lisp, that's silly: what better configuration format is there, than a programming language? Especially one tuned for expressing the app's domain? What a Lisper would expect a Lisp web-app library or game engine to do, is to build up and up from functional primitives into a high-level DSL, and then have the user write the final driver code in terms of the library's DSL. Then, the "configuration" is just the logic of that driver program—which can also drop out and replace any part of the library it wants, just by calling/passing a different function than the one you'd expect.

You can see this contrast in Emacs vs. vi:

• vi is a native program, that upon startup, parses a bunch of files in a custom configuration language, where each directive triggers its own special change to vi's internal state.

• Emacs is a Lisp runtime, where all the stuff that makes Emacs Emacs, is implemented as a set of ELisp libraries which call into said runtime. Each Emacs "major mode" is just an ELisp program that calls into those libraries. And your toplevel global configuration is just an ELisp program that defines the functions that are hooked to call into the major modes.

Thank you! This helps me understand the mentality a great deal better. It also helps explain some of the failure modes I saw Clojure teams I worked with fall into.
In this case I have been writing a lot of Java in the last few years. Nothing wrong with it, all the extra parentheses made it pretty good.
I've always found this thing of projects defining Clojure as a dependency of their project to be a artifact of that Clojure wants and aims to be a hosted language. So while it doesn't make much sense to have a Clojure project without Clojure included _somewhere_, it makes plenty of sense (in some specific use cases) to use Clojure as a library in a Java project with Maven, or JavaScript project with ClojureScript compiler as a library.

So with this, are there other languages where they are explicitly designed to be "parasites" like Clojure?

One could argue the many "compiled to JS" language are such, but they are always very hardcoded to JS, so not really parasitic like Clojure.

The "hosted" part is not quite right. Clojure is hosted, but what I think you are referring to with the "parasite" metaphor is called "embedded." (Distinct use of the term embedded from its use to describe small systems hardware).

Clojure was definitely designed to be both embedded and hosted, but those are distinct axes.

There are lots of languages designed to be embedded as scripting or automation runtimes in other systems. Tcl, Lua, Python, Guile (a lisp)...

Why embedded- pre-web, there used to be a practice of writing your infrastructure in systems language (C/C++) then writing your application tier in a scripting language. Countless games and GUI line of business tools on Windows were written this way.

Java in this case was considered a "systems" language, and there is a long history of embedded languages for Java. My favorite JVM-embedded language was called Beanshell.

This embedding was/is a use case for Clojure, important for "sneaking" Clojure in to the enterprise.

The "designed" part implies that the scripting runtime plays nicely with process-owned resources like threads and memory, and has a well defined interface for exposing your infrastructure objects to the scripting layer.

Post-web, if you squint, you can recognize this architecture in the DOM the browser (written in a systems language like C++ or Rust) exposes to its scripting tier (Javascript).

Lua, certainly. Lua is basically a C library that just so happens to have a standalone interpreter. It's largely intended to be embedded in other programs, which it does very well considering it is pure C with (basically) no dependencies.
Wouldn't this be less of an issue if you could say '[org.clojure/clojure ">1.2.0"]'? Or any of the other version number matching formats rubygems, npm, cargo, or any of the other package managers seem to manage?
You can, but this is discouraged. Unlike in other version management systems, Clojure tools do not use lockfiles; you're expected to be explicit about the version numbers you want.

https://nelsonmorris.net/2012/07/31/do-not-use-version-range...

Why no lockfiles? I'm sure there's a reason, I just don't know what it is.
This question merits a detailed answer that I can't provide right now, but here are some pointers:

https://hypirion.com/musings/lockfiles-are-not-the-only-opti... https://stackoverflow.com/questions/44521542/why-doesnt-grad...

All those links really tell me is that the problem lockfiles solve has been arbitrarily ignored. "A solution can exist, it's just not currently implemented" seems to be the state of things, unless I've misread it.

I'm all up for saying "thou shalt specify precise versions of library dependencies", but the specific case of `org.clojure/clojure` seems qualitatively different in that a precise version influences your consumers, and leaving it unspecified means your library might not work at all.

> Wouldn't this be less of an issue

I'm not sure I understand what issue you would solve with using "[org.clojure/clojure ">1.2.0"]" compared to what we're doing today, which is specifying the exact version we're using. What is the issue you're addressing here exactly?

In general, the Clojure community favors stability (more than others like npm) when it comes to dependencies, so you're almost always defining the exact version of the dependency you want.

The exact issue I'm addressing is where a library wants to specify that it can only be used with a version of clojure later than 1.2.0 (in this case). The problem the author is talking about is when a library forces a language version on its users because it either specifies no version at all, or has to specify a specific one.

I'm not sure I understand how `:scope "provided"` plays out if two dependencies say they want different versions of clojure, but that's also relevant.

> I'm addressing is where a library wants to specify that it can only be used with a version of clojure later than 1.2.0

That sounds like a solution to some problem, but I'm unsure of what that problem is. Not sure it makes sense to depend on more than one version at a time, because you're usually just testing with one version and can ensure compatibility with just one version.

Semver somehow made people believe that we can have versions numbers created with human-intuition but used as they are 100% true and generated by robots.

Oh yeah, semver is mostly bunk. You can get most of what you need from a single incrementing version number.

Look at it this way: say library A expresses a dependency on clojure 1.8, and library B on clojure 1.9. As the author of application C wanting to depend on both, what do I do when I want to use 1.10?