
Clojure: A Lisp that wants to spread - simonpure
https://simongray.github.io/essays/spread.html
======
tensor
Startup time isn't what is holding the language back. In my opinion it's:

1\. Tooling. You end up spending way more time getting your tooling setup than
it should be. 2\. Difficulty in learning. 3\. Clear best practices and
frameworks. The philosophy of composing libraries is extremely powerful, but
to gain broader adoption you still need straight forward frameworks and
opinionated best practices.

The last point has many unintuitive consequences. Clojure tends to attract
people who enjoy doing new things, or excelling in their craft. But mass
adoption also means catering to people who need to do "boring" things where
people just want to get their job done. For boring code, which is the reality
for many developers, they just want a clear best practice to get the stuff
done.

This could be a clear standard library or framework for a task, a clear
architectural pattern, and it certainly means easy to access code snippets.
There have been some attempts at these things, e.g. Luminous for the web, but
the community hasn't rallied around them with enough weight to make newcomers
comfortable.

So when faced with choosing: a testing library, any number of libraries
compose into a web service, any number of approaches to client side, then in
the end still having 100 equally good ways to architect a given piece of code
that requires thinking mathematically, well, it's daunting. Especially when
you are new to the language and just want to get your boring CRUD app working.

Maybe this is best summed up as saying that Clojure caters to the creative,
but to gain widespread adoption it also needs to cater to those stuck writing
boring code and just want to be done with it as fast as possible.

~~~
hota_mazi
I think you are omitting the main reason why Clojure never succeeded: it's
dynamically typed at the core.

It's on the wrong side of history in that respect.

I know it's trying very hard to catch up to statically typed languages now by
retrofitting some type system, but it's too little, too late.

Static types are where the current state of the art is, and we're not going
back. Clojure missed that train and will never catch it now.

~~~
slowmovintarget
Static types are the new religion, like OOP was in the 90s.

Types solve 2% of programming errors. They lead to coupling for the sake of
the compiler, and make code harder to change, harder to write, and often
needlessly constrain the utility of the code where they're applied.

But they do make the IDE code-completion go, so there's that.

~~~
exdsq
Can you reference "Types solve 2% of programming errors"? I've read numerous
research papers and blogs on this topic and have never seen anything as low as
2%.

Edit: Googled and nope, can't find anything.

~~~
didibus
Here are some which look at GitHub for various languages and count the defect
rates by analysing commit messages:

[https://dev.to/danlebrero/the-broken-promise-of-static-
typin...](https://dev.to/danlebrero/the-broken-promise-of-static-typing)

[https://www.i-programmer.info/news/98-languages/11184-which-...](https://www.i-programmer.info/news/98-languages/11184-which-
languages-are-bug-prone.html)

[https://nextjournal.com/PRL-PRG/toplas-analysis](https://nextjournal.com/PRL-
PRG/toplas-analysis)

One thing to note is that the absolute difference in terms of bugs from the
worst language to the best is still minimal. So language choice doesn't seem
to make or break software.

Another thing to note is that while overall static languages faired better in
terms of having lower defects, Clojure did best of all.

I knew of another one but can't find it again.

Then there are a few where they had beginner programmers implement trivial
programs in different programming language and checked how many errors/time it
took them. But I consider those all pretty bad since the experiments are so
reductionist. So I won't list them. They aren't conclusive either. Some end up
saying static and dynamic are same, some say dynamic is more productive at
equal defect, and some say static had less defects at equal productivity.

------
vemv
My personal perspective (after 8 years clojuring, half of that professionally)
is that Clojure keeps progressing, with ever better tools and ideas for
getting stuff done, optimally. I remain optimistic.

At the same time, it still fails at my "golden test": can I gather 5 random
freelance engineers and get them to ship a project within a few months,
wasting almost no billable time?

I can (and have) with Ruby, Typescript. People can learn those on the go,
being productive on day 3 or so.

Clojure is still bit of a journey on itself, involving quite a lot of stuff to
learn, and plenty of choices to make.

That's not necessarily a bad thing, nor Clojure's "fault", but it's a real
cost/risk that is still there.

I do envision a future where the mainstream is ideologically closer to
Clojure, and Clojure offers an (even) more polished set of tools than today.
With that mutual proximity, Clojure could indeed eat the world.

~~~
ken
Maybe I’m just dense, but I certainly have not been productive with any
language on day 3. (Not even Ruby, which feels comfortable faster than most
languages.) I sometimes _felt_ productive that soon but I always end up having
to rewrite once I learn the language fully.

I’ve learned enough languages that I can spot “a person’s first program in X”
now pretty easily.

~~~
vemv
My background is freelancing/consulting where it's not uncommon to gather
diverse individuals to work on a greenfield project.

There's an implicit assumption that all participants know a thing or two about
programming, and accordingly are apt learners.

So yes, on day 3 the code won't be perfect, neither on day 6 but after a few
code reviews one can get stuff done.

Personally I wouldn't be comfortable prescribing Clojure for such a project,
in contrast to other languages which I've never coded in, but that I know that
socially, today, work better.

------
shivekkhurana
I shifted to writing Clojure full time in November 2017 and feel that I've
improved greatly as a developer.

Other languages taught me how to solve problems their way, Clojure taught me
how to solve problems.

My biggest hurdle while getting started was lack of Newbie friendly resources.
And the language is kinda scary at first, specially if you are like me and
have spent 5 years working with Python or JS.

Dr. Fynmann said that if you want to get better at something, teach it.

Following his advice I've published multiple articles [1][2][3][4] and given a
talk about Clojure at a js conf [5].

The tooling is steadily improving. I don't think it will be as widespread as
JS but I doubt if it aims to be. Clojure seems to attract a certain kind of
developers anyways.

\---

[1] What I learned after writing Clojure for 424 days, straight
[https://krimlabs.com/blog/clojure-424-days](https://krimlabs.com/blog/clojure-424-days)

Learn Clojure by building a drug dealer api [2]
[https://krimlabs.com/blog/clojure-drug-dealer-
part-1](https://krimlabs.com/blog/clojure-drug-dealer-part-1) [3]
[https://krimlabs.com/blog/clojure-drug-dealer-
part-2](https://krimlabs.com/blog/clojure-drug-dealer-part-2) [4]
[https://krimlabs.com/blog/clojure-drug-dealer-
part-3](https://krimlabs.com/blog/clojure-drug-dealer-part-3)

[5] If you are going to transpile JS, why not use ClojureScript?
[https://www.youtube.com/watch?v=Bs44qdAX5yo](https://www.youtube.com/watch?v=Bs44qdAX5yo)

------
lbj
Thats last few sentences make no sense. The argument is that uptake is stunted
by the JVM's slow startup time, making it unsuitable for commandline utils or
desktop apps. However thats easily fixed which was never more hillariously
stated than when Rich Hickey did it a decade ago. A blogpost went viral and it
was a thunderous critique of Clojure, focused on this single point of slow
start up. In the comment section was only 1 reply from Rich:

time java -client -jar clojure.jar helloworld.clj

> system time 0.0001s

The -client param makes all the difference :)

~~~
mistersys
Uhm.. I want to give the benefit of the doubt, but frankly this seems
deceptive, or at least out of date, unless I'm totally missing something.

Why is there no magical --client / \--script option for the Clojure ClI to
make startup faster, if this is such an easy solution? Also, you show system
time, but the time to load dependencies is in user time, which is slow by the
nature of Clojure.

Example:

    
    
        $ time java -client -classpath /usr/local/Cellar/clojure  /1.10.1.492/libexec/clojure-tools-1.10.1.492.jar clojure.main hello.clj
        Hello, world
        java -client -classpath  clojure.main scripts/script.clj    1.39s user 0.08s system 187% cpu 0.788 total
    
        $ time java  -classpath /usr/local/Cellar/clojure/1.10.1.492/libexec/clojure-tools-1.10.1.492.jar clojure.main hello.clj
        Hello, world
        java -classpath  clojure.main scripts/script.clj  1.37s user 0.09s system 180% cpu 0.811 total
    

(Excuse the details of my Clojure install location above)

User time appears slightly slower without the -client option, but probably is
not statistically significant. Also, anyone who has worked on more than a toy
project with Clojure knows that it's not just about the Hello World startup
speed, every dependency you add has a significant impact on Clojure startup
time.

~~~
stingraycharles
As mentioned in other comments, the -client option is ignored on 64 bit JVMs,
so it has no practical effect. Giving the benefit of doubt, Rich Hickey must
have been using a 32 bit JVM.

~~~
thu2111
That's not quite it. The client/server distinction was removed from the JVM
_many_ years ago. These days the JVM switches between modes on the fly on a
per-method basis, this is called tiered compilation, so the optimisations are
in effect on by default. Back in 2006 yes it may have made a big difference
but it wouldn't have made startup magically instant.

IIRC Clojure apps are slow to start because they do a lot of first-time setup
work that isn't actually executing user code. It's not so much a JVM problem
as a Clojure-emitted-code problem. The GraalVM native-image tool fixes it
because it can start an app in a pre-initialised state.

~~~
stingraycharles
I understand, but either way, the example Rich Hickey used to execute the
sample Clojure application in less than a millisecond seems impossible?

In other words, if Rich' example is real and recent, what could possibly be
the environment in which this worked?

~~~
thu2111
Yeah. Not sure - maybe it dates from a time when clojure was just a toy or
prototype or something.

------
Random_ernest
I really like Clojure, it's a well designed language and one can get quite
productive, surprisingly fast.

My core criticism that is not really mentioned in the article is the error
messages. At the beginning I often felt lost and had no idea where to look if
something went wrong.

~~~
innocentoldguy
I like Clojure too but its less-than-useful error messages are what got me
interested in Elixir. I still get Lisp-style macros with a better syntax (for
me) and outstanding error messages (generally speaking).

------
PaulRobinson
I used to work at a large Clojure shop. I've moved on, and I've heard
anecdotally that they are building most new stuff in Go.

Barrier for entry and continued use in that limited experience was:

\- Learning curve for new developers is very high in comparison to other
languages, so moving existing engineers over sucks.

\- Experienced Clojure developers want a job where they can "do Clojure", not
necessarily because they're interested in the problems the business was
created to solve, so hiring sucks.

\- Lots of conversations turn to "the Clojure way" to solve a problem rather
than using industry accepted protocols, standards and tooling. I mean, OK, but
this makes interacting with the rest of the World a bit sucky.

\- Most libraries never get to v1.0. Most people write 0.0.1 and may do some
minor updates so you see it get to 0.0.5 and then it stays there. That's
probably because it all 'just works', but it scares most devs when they see
that and are about to bake it into their next release.

\- Deployment is a known quantity and the JVM is useful in this regard, but it
doesn't have the pure simplicity that some other runtimes do. Clojure
implementations in other runtimes (Joker, Clojerl, etc.), might help or hinder
here.

Startup time is a problem on CLI and desktop apps, sure. At my ex-employer,
one server deployed app took several minutes to start and that had challenges.

That was not - I think - the reason the team started to look elsewhere or the
reason I'm more likely to pick up Ruby, Python, Go or Elixir for my next
project. There are lots of rough edges like the ones I identify above - many
of them cultural - that need to be smoothed out a little, I think.

I look forward to functional methods becoming mainstream (again?), and Clojure
could get there, so I wish the community luck.

~~~
thu2111
_Experienced Clojure developers want a job where they can "do Clojure"_

This seems like a common problem with functional languages in general. I've
heard the same thing about Haskell, repeatedly. Such devs search out a place
where a Haskeller/Clojurist got into a tech lead position and then all pile
in, before you know it the team is spending half its time writing DSL
compilers instead of adding features to the product.

 _I look forward to functional methods becoming mainstream (again?)_

Has it ever been mainstream? AFAIK it's always been this basically strange
offshoot of the family tree that split off a long time in the past.

The core problems you highlight don't have any obvious solution,
unfortunately. Learning materials can be improved but ultimately Lisp is very
old and very unlike more modern languages. Libraries not getting to 1.0 is a
symptom of a small community that derives more satisfaction from an
intellectual exploration than having lots of users, combined with lack of
commercially driven outcomes. Startup time I guess can be solved with
technical solutions (and is being solved, as a side-effect of other projects
elsewhere in the JVM ecosystem).

------
ooooak
1\. In India, there are only ~100 jobs for a Clojure developer. That includes
people just throwing in Clojure just to hire Java/Scala developer.

2\. The learning curve for the Clojure. Clojure is simple but it's not easy by
any means.

3\. JVM interop if you are not a java developer it adds in more learning time.
Most of the Clojure libraries use java heavily. It's a good thing but it adds
in an extra layer of learning java's ecosystem.

4\. Error messages. Even after years of Clojure. I still find it hard.

5\. Documentation. Most of the packages I use don't have dedicated websites or
good documentation.

Even after all the issues. It's still worth the investment. I started working
on a crawler for a freelance gig. we first built it using Golang. But due to
its complexity and lots of bugs due to mutation. We ported it to Clojure and
we are not looking back.

~~~
Heliosmaster
To be honest, I've done Clojure full-time (and exclusively) for 6 years now.
I've almost never had to use Java interop. (With CLJS I did a little more JS
interop).

There were always wrappers written by somebody else that always did what I was
looking for.

------
pgt
The most interesting Clojure-like language around is, IMO, Carp[^1]. Carp is a
Lisp written in Haskell that compiles down to C with Rust ownership semantics
and looks like Clojure. So you get a GC-less Lisp with fast startup time that
is suitable for game development with the safety guarantees of Rust.

[^1]: [https://github.com/carp-lang/Carp](https://github.com/carp-lang/Carp)

~~~
hcarvalhoalves
It doesn't seem to have immutable data structures by default and concurrency
primitives. That changes greatly how you write algorithms and structure your
program.

It's cool, but it's like the complete opposite of the main ideas behind
Clojure.

------
anmonteiro90
It'd be interesting to back the claim that "Clojure is (slowly) eating the
world.".

Interestingly, that's absolutely not my perspective and I don't see any
sources for that statement in the post.

~~~
simongray
I was talking about its ever expanding reach. It was more of an intertextual
reference to the Andreessen Horowitz classic than an ideological statement
about the popularity of Clojure, that is why I also added the (slowly).

~~~
anmonteiro90
That makes sense but it's not what comes across. The sentence immediately
after says that "the language continues to grow". That's what I'm questioning.

~~~
simongray
That is my impression, especially 2019 seems to have been a turning point
after 1-2 years of relative stagnation, but I get that you feel differently.
That's why you switched to OCaml ;-)

~~~
Scarbutt
Looks like its peak was in 2014[0] and since then it has been declining.

[https://trends.google.com/trends/explore?date=all&geo=US&q=c...](https://trends.google.com/trends/explore?date=all&geo=US&q=clojure)

~~~
iLemming
How is it declining? Every single year we're adding 3-5 new conferences, last
year alone - Ukraine, Russia, Brazil, India, Canada. New books being
published. There are number of active podcasts (more than for any other FP
lang). Latest JVM survey shown - Clojure has become more popular than Scala
(largely due to Kotlin), but still surprisingly so. There's a Clojure related
post on HN top, almost every week, sometimes multiple times a week.

Sure, it ain't Typescript or whatever, but it's definitely not declining.

~~~
piokoch
Maybe it just means that there is a small and vocal community around Clojure -
being on HN means that something is interesting for HN community, but it does
not translate to wider adoption necessarily.

When Clojure was started it didn't have much competition in JVM languages
space. There was Scala only. Right now we have also Kotlin, which seems to be
hitting the sweet spot between being a better Java and being difficult to
grasp (like Scala or Clojure). In addition Java alone is getting better, so it
is harder and harder to eat its cake.

~~~
iLemming
> Maybe it just means that there is a small and vocal community around Clojure

There's relatively small but vocal Emacs community. There are no books being
published; almost no podcasts; it took forever to organize a conference, but
they still couldn't find a venue, so it was a video-conference. Every few
years there's a new "Emacs killer" but the ecosystem (after over 40 years) is
very much alive and thriving.

Same thing can be said about Clojure. It's a very vibrant ecosystem and
there's a lot happening in Clojuresphere, but of course, with almost every
post about Clojure, there would be at least one person to claim - "Clojure is
dying." Well, if that's true, then buckle-up folks. It's going to be a very
long, steady ride.

------
maehwasu
As someone who uses Clojure a lot:

It would be a lot better for language adoption if people would write more blog
posts showing best practices for tooling, and less vague stuff about how
awesome the language is.

For example, the startup time thing is completely irrelevant if you use
Emacs/VSCode with a standalone REPL that you connect to.

It's non-trivial to set this up (more because of lack of consolidated info
rather than time it takes), but it can easily be standardized across
machines/envs (I Dockerize all projects in a simple way to allow team members
to get started up with REPLs easily).

~~~
dragandj
I totally agree, and that's why I am going to write a book about that:

[https://twitter.com/draganrocks/status/1226811229362147331](https://twitter.com/draganrocks/status/1226811229362147331)

It has to wait until I wrap up the books that are currently in progress [1],
but I expect to start this summer.

[1] [https://aiprobook.com](https://aiprobook.com)

~~~
maehwasu
Awesome.

To put my own money when my mouth is, here is the bash script to start up a
nREPL server in Docker:
[https://pastebin.com/PPmfDPyA](https://pastebin.com/PPmfDPyA)

Here is the Dockerfile:
[https://pastebin.com/ymcUFdYT](https://pastebin.com/ymcUFdYT)

And here is a sample deps.edn with the cider alias:
[https://pastebin.com/2a7vSFD7](https://pastebin.com/2a7vSFD7)

To run this, you'd just put them all in one directory, run cider-up.sh, and
then connect to the REPL with Cider in EMACS, or in Calva for VSCode with
"Jack in to or Connect to REPL Server" and then "localhost" "4444"

------
didibus
For me, this article is spot on. I had been looking for a long time for my one
language I can use for everything, and Clojure finally (just recently) got
there. I can use it for scripting, writing command line apps, making backend
services, writing web front-ends, making desktop applications with GUI, toying
with generative art, doing data-science, making games, and more.

It really has great reach, and its only getting better. Similarly, in terms of
programming language, you can use it to explore many paradigms and innovative
ideas, like CSP, logic, probabilistic, functional, meta, concurrent, dynamic,
typed, contracts, data-flow, OOP, condition systems, monads, etc. All this
keeps me interested and constantly learning. Never having to wait for features
you miss or wish you had.

I can see myself sticking with Clojure for a long time.

------
rubyn00bie
Last week some nice folks assured me tooling was good in Clojure; and not a
total time suck, and so I'm getting ready to take the jump I think... but, one
thing I'm still really skeptical about... is embodied in this paragraph:

> The way the languages are integrated today, Clojure developers doing full-
> stack development don’t really have to think about data
> serialisation/deserialisation. Writing code for frontend and backend differ
> mostly just in the way that the different implementations access the host
> platform. The functional aspects of Clojure, especially the immutability and
> focus on referential transparency, ensure that source code is mostly split
> into chunks of highly portable code, with the host-interop conveniently put
> aside in its own sections. You can move code between frontend and backend at
> your leisure.

Is it really that good using Clojure across environments and runtimes?

I ask because I tried all of this with Scala years ago (I loved the language)
and Ruby to an extent, but it was pretty miserable or rather there was really
no reward but more effort on my part. I.e. I'd spend more time annotating,
bridging, and learning how to interact with the different runtime than I would
writing actual feature code[1].

Or like what are the killer libraries everyone loves? Do they
(libs/frameworks) support different clojure runtimes ("hosts?") outta the box?
Is it easy to write "pure clojure" projects that work for any clojure project?

The one I know about or have heard most about was Datatomic, which always
sounded freakin' brilliant, but I don't fucks with non-free databases. Arcadia
I just saw and it looks cool but, it also seems like a dumb mountain to climb,
if that's how I wanted to start screwing around with Clojure.

[1] Tools like Scala/ScalaJS and RubyMotion (back in the day, heh), were the
reasons I ended up a polyglot. They're great tools, if you know the platform,
if you don't, you're pretty much still fucked... but once you know the
platform it's kind of hard to want to use them.

~~~
scarredwaits
I can attest that full stack (server-browser) Clojure code sharing works very-
very smoothly. I've been developing web applications like that for some time
now, and apart from less context swithing (because it's the same language on
both sides), sizeable parts of the code are cross-compiled to run both on the
browser and the JVM with very little or no extra effort.

Example 1: I'm using Clojure's Spec library for validation. There is code that
is used on the browser to validate the input that users enter into forms, and
the same exact code is used on the HTTP endpoints that are called when the
user attempts to submit the form.

Example 2: I'm using the Tongue library to provide client-side translations of
the whole UI, but the same data files and library are used in some cases on
the server to generate files that contain translated strings.

Example 3: The web UI logic is written using pure data manipulation (thank the
Re-frame library for that) and because of that we are able to unit test it on
the JVM without having to go through the complecity of launching and driving a
browser on our CI server.

~~~
rubyn00bie
Thank you so much for the response!

Number 3 has me totally stoked; since that's incredibly valuable-- or like a
dream to me to be honest, could you speak more about how that works? Is it
just from using re-frame (which I'm looking at as I write this)? Is it able to
test for visual regressions because of the pure-data UI? Or like how have you
found that testing functionality in practice has it been saving y'all a lot of
bugs and headaches you think?

~~~
rufugee
Agreed... would love to hear more about this as well....

~~~
simongray
Check out my reply to rubyn00bie.

------
abrax3141
I don’t actually get the big deal about enforced immutability. Of course, you
need to constrain yourself in certain circumstances to do things immutably,
but it seems like something that should be contextual (immutably ...), rather
than enforcing it. In fact, sometimes it is necessary in high performance
multi—threaded/multi-processor environments to use side effects judiciously.
Moreover, most serious lisp programmers almost write immutably by default. The
only (common) exception being hash tables, but you just don’t use them if you
need more control.

~~~
_bxg1
> sometimes it is necessary in high performance multi—threaded/multi-processor
> environments to use side effects judiciously

The short answer is: Clojure (and other languages/frameworks that emphasize
immutability) is not intended for those use-cases.

The long answer is, Clojure added something called "transient data structures"
as a trap-door for cases where you really absolutely need mutability for
performance:
[https://clojure.org/reference/transients](https://clojure.org/reference/transients)
But they are really intended to be the exception, not the rule.

Enforced immutability is a conscious choice you make to trade some performance
for increased robustness. Also, compared to "immutability by convention",
enforced immutability allows certain optimizations to be made that greatly
decrease the wasted memory and allocation/deallocation that would occur if you
naively cloned everything.

~~~
abrax3141
I haven’t really thought this through, but at the OS level we used to have the
concept of reentrancy, which separated code from variables for multi-tasking.
I haven’t seen this exact concept in a programming language level, although I
don’t know every language, and it may exist by another name. I didn’t really
think about this until now, but I naturally write reentrantly, separating the
hash tables and globals For cross thread data, and writing everything else
functionally, and/or using the thread system to manage all other mutables as
locals (like the transient construct, I guess).

~~~
_bxg1
I think the idiom in Clojure is for threads to be functional, so they aren't
running persistently and mutating shared state: they spin off to do
nonblocking work and then return their result. While doing so they _share_
memory so as to avoid serialization/cloning overhead, but that shared memory
is an immutable data structure, so you don't really have to worry about race
conditions because threads can't step on each other's toes.

------
Zelphyr
What I want to know is where to find a Clojure and/or ClojureScript job. I got
a taste of Clojure on a project late last year and became enamored with it. So
much so that I would love to find a job where I can use it at least part of
the time.

~~~
emmanueloga_
Try the #jobs channel of
[https://clojurians.slack.com/](https://clojurians.slack.com/)

------
svnpenn
The situation with windows is weird. You have to run a powershell script to
install. They should just offer a Zip or Exe like every other programming
language.

[https://github.com/clojure/tools.deps.alpha/wiki/clj-on-
Wind...](https://github.com/clojure/tools.deps.alpha/wiki/clj-on-Windows)

~~~
iLemming
> The situation with windows is weird.

Isn't it always? Basically anything that's not related to .NET has its warts
in Windows. Erlang for example.

~~~
pjmlp
Delphi, Python, Ruby, C++, Qt, Perl, nodejs, Java, Pharo/Smalltalk, Eiffel,
Common Lisp, Ada do pretty well on Windows, without being .NET related.

~~~
iLemming
Funny, I'm probably getting old, but I do remember times when every single one
of these had issues in Windows. Besides, all of them are much older than
Clojure.

------
zerr
While we are at it - how do you refactor a mid-sized/large Clojure project?

~~~
didibus
So, my first answer is that you shouldn't have too, and if you do, you might
not be writing proper Clojure code.

The most fundamental concept in Clojure, from the famous Rich Hickey talk
Simple Made Easy ([https://www.infoq.com/presentations/Simple-Made-
Easy/](https://www.infoq.com/presentations/Simple-Made-Easy/)) is that your
code should strive to be decomplected.

That means that your program should be made of parts that when modified do not
break anything else. This, in turn, means you don't really ever need to
refactor anything major.

In practice, this has held true for most of my code bases.

Now, my second answer, because sometimes there are some small refactors that
may still be needed, or you might deal with a Clojure code base that wasn't
properly decomplected, you would do it the same way you do in any dynamic
language.

The two things that are trickier to refactor in Clojure are removing/renaming
keys on maps/records, and changes to a function signature. For the latter,
just going through the call-sites often suffice. The former doesn't have that
great solutions for now. Unit tests and specs can help catch breakage quickly.
Trying out things in the REPL can as well. I tend to perform a text search of
the key to find everywhere it is used, and refactor those places. That's
normally what worked best for me.

It helps a lot if you write your Clojure code in a way that limits how deep
you pass maps around. Prefer functions which take their input as separate
parameters. Prefer using destructuring without the `:as` directive. Most
importantly, design your logic within itself, and so keep your entities top
level.

~~~
iLemming
> Prefer functions which take their input as separate parameters.

In practice, it's better to avoid positional arguments and extensively use
maps and destructuring. Of course, there's a risk of not properly passing a
key in the map, but in practice that doesn't happen too often. Besides - Spec,
Orchestra, tests and linters help to mitigate that risk.

~~~
didibus
> In practice, it's better to avoid positional arguments and extensively use
> maps and destructuring

We can agree to disagree I guess. In my experience, especially in the context
of refactoring, extensive use of maps as arguments causes quite a lot of
problems. Linters also do nothing for that.

Positional arguments have the benefit of being compile errors if called with
wrong arity. I actually consider extensive use of maps a Clojure anti-pattern
personally. Especially if you go with the style of having all your functions
take a map and return a map. Now, sometimes, this is a great pattern, but one
needs to be careful not to abuse it. Certain use case and scenarios benefit
from this pattern, especially when the usage will be a clear data-flow of
transforms over the map. If done too much though, all over the app, for
everything, and especially when a function takes a map and passes it down the
stack, I think it becomes an anti-pattern.

If you look at Clojure's core APIs for example, you'll see maps as arguments
are only used for options. Performance is another consideration for this.

Doesn't mean you should always go positional, if you have a function taking
too many arguments, or easy to mix up args, you probably want to go with named
parameters instead.

~~~
iLemming
For example if you have something like:

    
    
        (study [student age] ,,,)
    

And inside it calls a bunch of auxiliary functions where you pass either
`student` or `age` depending on what those functions do, then someone says:
"oh we need to also add an address", and have address verification in the
midst of that pipeline. And instinctively programmer would add another
positional argument. And to all auxiliary functions that require it. The
problem with the positional arguments - they often lie, they're value depends
on their position, both in the caller and in the callee.

It also makes it difficult to carry the data through functions in between. The
only benefit that positional arguments offer is the wrong arity errors (like
you noted). And yes, passing maps can cause problems, but both Joker and Kondo
can catch those early, and Eastwood does that as well, although it is
painfully slow. With Orchestra and properly Spec'ed functions - the missing or
wrong key would fail even before you save the file. I don't even remember the
last time we had a production bug due to a missing key in a map args.

But of course it all depends on what you're trying to do. I personally use
positional arguments, but I try not to add more that two.

~~~
didibus
That's a bit of a different scenario then I was thinking.

In your case, you're defining a domain entity, and a function which interacts
on it.

Domain entities should definitely be modeled as maps, I agree there, and
probably have an accompanying spec.

That said, still, I feel the function should make it clear what subset of the
entity it actually needs to operate over. That can be a doc-string, though
ideally I'd prefer it is either destructuring and not using the `:as`
directive, or it is exposing a function spec with an input that specifies the
exact keys it's using.

Also, I wouldn't want this function to pass down the entity further. Like if
study needs keys a,b but it then calls pass-exam which also needs c and d.
This gets confusing fast, and hard to refactor. Because now the scope of study
grows ever larger, and you can't easily tell if it needs a student with
key/value c and d to be present or not.

But still, I feel since it's called "study", it feels like a side-effecting
function. And I don't like those operating over domain entities. So I
personally would probably use positional args or named parameters and wouldn't
actually take the entity as input. So if study needs a student-id and an age,
I'd just have it take that as input.

For non side-effecting fns, I'd have them take the entity and return a
modified entity.

That's just my personal preference. I like to limit entity coupling. So things
that don't strictly transform an entity and nothing else I generally don't
have them take the entity as input, but instead specify what values they need
to do whatever else they are doing. This means when I modify the entity, I
have very little code to refactor, since almost nothing depends on the shape
and structure of the entity.

------
abrax3141
If you want fast startup times, just live in emacs, keeping multiple shells
alive underneath. You can build yourself a whole clojure repl that just lives
there all the time (startup once), and a bash or whatever you like as well.

------
gentleman11
Why don’t lisp fans use older lisp implementations like Common Lisp or scheme?
Is it just a lack of libraries and frameworks?

~~~
peatmoss
Some of it is libraries and frameworks. Some of it is the attention Clojure
has paid to ergonomics. A lot of Clojure’s libraries seem to have been built
by smart people for mediocre programmers (like me!). Elsewhere in lisp land it
can feel like smart people wrote libraries for themselves.

For me Racket is probably the closest thing to a decent end-to-end modern lisp
experience with decent libraries outside Clojure. Gerbil Scheme also looks
promising.

For me I’m most often writing smallish standalone apps, which I feel are
easier to make in Racket than Clojure. But between the two languages, I’d
probably take Clojure if I didn’t have the JVM along for the ride. Of course
the JVM is also one of Clojure’s biggest strengths. Also, I don’t trust Oracle
enough to stop pretending that Graal doesn’t exist.

~~~
pron
Aside from being faster, more mature and with better observability, why is
bringing "the JVM along for the ride" different from bringing Racket's runtime
along? Also, you do know that Oracle is behind OpenJDK, has been for ten
years, and has recently made the JDK completely open-source and free of field-
of-use restrictions for the first time in Java's history?

~~~
higerordermap
Off topic; But since you work at Oracle, what is the Oracle copyright terms on
alternative implementations of Java? (in the light of Android lawsuits). Eg:
some research or commercial implementation for a subset of java etc?

~~~
pron
What do you mean by "alternative implementations"? OpenJDK is 100% open
source, and is entirely governed by its open-source license. You can do
whatever you want with -- including take just big or small pieces from it and
change them -- it as long as you comply with the license. If you want to use
the name "Java", your software must pass the TCK.

~~~
higerordermap
No. By alternative implementation I meant eg: some experimental java compiler
to a different target eg: say JS / wasm, or a memory efficient class library
implementation etc..

~~~
pron
The Java spec is not open source. You could either extract as much or as
little from OpenJDK _and comply with its open-source license_ or obtain a spec
license if you don't wish to open-source your implementation. In other words,
you have the open-source route or the closed source route, but the closed-
source route isn't free. (BTW, I am not authorized to speak on behalf of
anyone other than myself, so this is just my opinion).

------
Jeaye
For anyone struggling with dynamic typing in Clojure, thinking it's impossible
to manage and even harder to build confidence in your code, please look at
Spec and Orchestra. You can easily build predicative contracts for all of your
data and know as soon as anything "goes wrong" in terms of your data shapes.

I think everyone using Clojure JVM, ClojureScript, or Clojure CLR should be
doing this.

------
raz32dust
I am not sure I buy into the argument that startup time is the hurdle to
Clojure adoption. If that were the case, we would have at least seen more
adoption for server use cases. Applying Occam's razor, I think Clojure is not
popular for the same reason that common lisp is not popular - it is simply too
hard. Functions and immutability are a more difficult abstraction to grasp
compared to objects and procedures and mutable state. The overhead of finding
quality talent is too high, and likely always will be because of this.

~~~
iLemming
Clojure is not hard. It's just different. People immediately reject anything
that's different. Just like when Fibonacci tried to convince people in 13th
century to use indo-arabian numerals.

I've seen people with no prior programming experience learning Clojure within
a few days. And I've seen senior software developers struggling with it for
months.

------
ertucetin
I think Clojure is going to be a spec for Lisp family in the future.

~~~
TurboHaskal
You probably have only scratched the surface of Lisp if you really think that.

------
pron
Can we please stop using the verb transpile? Compile is fine, thank you.

When it was used only for certain kinds of "shallow" compilation it was bad
enough (and it was really horrible even then), but this article uses it when
describing compilation with whole-program analysis and optimization and
machine code as the target.

~~~
7thaccount
There's absolutely nothing wrong with transpile. Yes compiler covers all sorts
of things (code -> assembly or code -> different language code...etc). However
when I hear "transpile" I immediately think of a language going to another
language like when Nim gets converted to C or JavaScript or when Clojure gets
converted to Java first. It is a specific form of compilation right?

~~~
pron
It is a rather ill-defined form of compilation; i.e., it's not clear _what_
that form is. Compilation means translation from one language to another;
every compiler does that. Transpile was born because people were used to the
fact that the target language of many compilers is machine code, but it's
entirely unnecessary to have a separate word for that, as it doesn't add any
information. There could be a meaningful term for the word, to refer to some
sort of "shallow" compiler, that compiles a source language to a target
language of a similar level of abstraction and preserves the source code
structure in the output, but people often use the word in other cases, too. In
other words, transpile and compile are synonymous, making the newer, hybrid
word redundant.

~~~
7thaccount
I think this is just the way human language generally works being that it is
organic and messy. In my field, there are numerous overlapping and even
contradictory terms.

I like the added distinction "transpiler" provides as I immediately know what
the author means. Or the author could just use the term "compiler" plus
another sentence to describe which kind. Transpiler seems like the more
efficient way to convey the concept.

~~~
pron
There is no "kind" though. "It compiles C to JS" and "it transpiles C to JS"
mean the exact same thing. No added clarification is necessary, because there
is just no distinction between the terms. On the contrary, if people think
that "transpilation" has some distinct meaning, they are just being confused
about what compilation is.

------
lincpa
You need a well-organized linear pipeline system, this is "The Pure Function
Pipeline Data Flow v3.0 with Warehouse / Workshop Model"

~~~
engineeringwoke
I hope you realize that this is complete nonsense to anyone that isn't part of
your new "new" functional programming cult. It has almost no semantic meaning.

~~~
hellofunk
I definitely saw nothing but sarcasm in the comment you are replying to.

But now after looking at their other comments, I see it was not sarcasm.

What an understatement for how messy functional programming can actually be.

~~~
engineeringwoke
I thought he was joking at first too and googled it to find out that he was in
fact serious.

~~~
hellofunk
The quoted part reminds me of the fizzbuzz enterprise edition.

~~~
lincpa
Unfortunately, you treat the quote as a joke, and you have no ability to apply
the quote at all.

~~~
hellofunk
You are making unfair and uninformed assumptions about me, someone you do not
know. I've worked professionally as a full-time Clojure developer for many,
many years.

> you have no ability to apply the quote at all.

You don't know what my abilities are.

~~~
lincpa
show you github or blog, Time is not ability.

~~~
harperlee
Your github is full of text and cloned repos... and you refused my earlier
suggestion of sharing practical examples of your Tao, so it seems strange that
you are so quick to demand proof of competency through code just to discuss
with you.

~~~
lincpa
If I give an example of one aspect of a topic and students cannot understand
other aspects, then I will not repeat the explanation or example.

    
    
            ---- Confucius, a famous Chinese educator
        

Imagination is more important than knowledge.

The true sign of intelligence is not knowledge but imagination.

Logic will get you from A to B, imagination will take you everywhere.

    
    
            ---- Albert Einstein    
    

Confucius and Albert Einstein mean the same thing. I have enough examples. If
you still do n’t understand, you are not suitable for learning my theory.

My github is mainly my blog and my project homepage, and other code bases are
just collections of other excellent developer projects.

I think hellofuck is not good, but he is not convinced, so I think he can show
himself with blog or github.

~~~
lincpa
Typographical error:

1.hellofuck -> hellofunk

2.Show you github or blog -> Show your github or blog

Finally, you try to downvote my comments, and it doesn't make any sense, you
should know that everyone's talent is different, just as your talent doesn't
suitable for learning my theory, Or rather, you have no fate with the Tao.

------
lincpa
Clojure is a FP (Lisp) language based on RMDB theory, which is very suitable
for dataflow and data-driven programming, so it is suitable for large-scale
industrialized pipeline software development using Warehouse/Workshop Model
model.

[https://github.com/linpengcheng/PurefunctionPipelineDataflow](https://github.com/linpengcheng/PurefunctionPipelineDataflow)

~~~
harperlee
In the last ~18 months you have been /very/ energetic in promoting your theory
here, I have some alerts since about a year and you are always popping up with
the same link. Looking into your posting and commenting activity, there not
much more.

By now I guess that you have had enough exposure, but most of your comments
and links to github have not sparkled a lot of interest or comment, and
reception has been skeptical / lukewarm.

Take this as just one datapoint but for me your insistent reposting is more
noise than any other thing. If you really care about your theory that is cool
and all, but may I suggest that you create something like some practical
application examples in blog form, edit them thoroughly, and then post them as
new topics, intead of polluting "related" conversations? That way you can
better gauge what interests this community, and what not.

By the way interleaving english and chinese such as in your repo README.md
makes for an unpleasant read - better to separate both in two distinct
documents.

Best luck!

~~~
lincpa
I noticed that you have not posted any useful technical comments in the last
100+ days. Sorry I didn't keep reading.

In fact, this theory has been unprecedentedly hot recently. Star and traffic
on github have risen sharply. On 2020-02-27, it was published by editor on
reddit/devopsish, and other clojure communities.

My theory is written for a few people who understand Chapter 2 of the "Code
Complete" and can read it without reading other chapters, not for people who
cannot understand Chapter 2 and focus on other chapters.

So I have stated in the article: I will not write too many application
examples or patterns. Because people who like to read such articles can't
learn my theory.Because they lack imagination.

```

Imagination is more important than knowledge.

The true sign of intelligence is not knowledge but imagination.

Logic will get you from A to B, imagination will take you everywhere.

    
    
            ---- Albert Einstein
    

```

------
zelly
I tried Clojure but ended up hating it for reasons all stemming from its
hosted implementation. Whenever I didn't know how to do something, I (and most
other Clojure users) would go use some JDK class (or worse, an external Java
library) instead of figuring out how to write the Lisp.

For some reason this doesn't often happen with regular Lisp even though CFFI
is available. There's a tendency to just reimplement it directly in Lisp,
which is the more maintainable option.

~~~
simongray
I have to say that I don't really recognise that thing about using JDK
classes. I've worked with Clojure for 2 years on a big government project
(maybe 50 Clojure devs) and it was full-stack Clojure all the way down. We
never used any Java libraries either, but of course basic Java/JS interop is a
pretty normal occurrence in Clojure when doing a certain type of code (like
reading and writing certain binary files to disk, you'd probably reach for the
Java Standard Library). For 99% you need no interop in Clojure. ClojureScript
is a bit different as you often do need to access HTML elements or various
singleton objects like js/window or js/document and have to use JS interop.

~~~
oulu2006
I have to concur with you mate, I've used Clojure now for 6 years in 2
startups and I can almost count on 2 hands the number of times I've had to
interop directly with Java classes to get something to work.

------
yarrel
The dealbreakers with Clojure for me are:

1\. Weirdly irregular syntax.

2\. Java import statements and Java error backtraces anywhere you want to do
real work.

3\. Inexcusable renaming and name collisions on basic Lisp functions.

It's much, much less of a Lisp than Scheme and neither "but it's properly
functional" nor "we fixed the tooling now" make that any less true or the
language any more interesting for the use cases of using a Lisp or targeting
the JVM with a more expressive language.

~~~
akra
I do agree that initial impressions are important for a language and
"perceived" learning curve does make a difference. Things that scare people
away initially do hurt adoption; things like Clojure's syntax and that it is a
dynamic language on the JVM (i.e. how does that affect
interop/performance/etc) all raise questions. Even if there are answers to
these questions and justified reasons just like most sales pitches you've
already lost a lot of people who have doubts and are already productive as
they are.

As an example I remember a presentation done some time ago where the presenter
was showing code in F# and many non-technical people understood the code
thinking it was a pseudo code at first (i.e. business stakeholders/BA
types/etc). That was a win for them. If I showed F# and Clojure code side by
side to an ex-coder manager as an example syntax I'm sure would matter and
they often have a say in this decision. Buy-in is an important criteria in
language selection for sure, as well as current skilled developers. If the
pool of devs is small how easy it is to train people and get people wanting to
be trained in it thinking it may be dead end skill with no jobs? I don't have
that opinion personally but some dev's definitely do. Especially because jobs
in these languages are scarce; if learning curve is small then it could be
used even then.

My cynicism shows marketing and perception of a tech stack are equally if not
more important than other factors in tech selection in many companies. No one
got fired for picking IBM or in this day and age Java?

