Welcome! Please see the About page for a little more info on how this works.

+2 votes
in Clojure by

I am a big fan of Clojure for its simplicity, muti-thread models, language stability, and excellent Java interop. But I am also an even bigger fan of static typing, which is extremely helpful for projects with more than a few developers.

TypeScript adds an excellent layer of type check for JS that could help eliminate many programming errors at compile time. And it also opens up lots of editor / ide tooling possibilities. I would say that TypeScript is the best thing ever happened to JS.

I am wondering if Clojure core team or any party with serious capability and resources is considering developing something like TypeScript for Clojure?

If not, why?


1 Answer

+5 votes

The Clojure core team has no plans to work on static typing for Clojure as we don't think it's necessary. Our effort is focused in spec (https://clojure.org/about/spec), which provides support for describing optional specifications for Clojure data and functions.

The Typed Clojure project (https://typedclojure.org/) is a fairly large effort by Ambrose Bonnaire-Sergeant that was done while he was working on his PhD etc. You may also be interested in Spectrum (https://github.com/arohner/spectrum) which is static type checking based on specs. Neither of these are widely used or in particularly active development afaik.

edited by
Thanks for the answer.

We did study spec and found out it is mostly a run time thing. It's very helpful for documentation and testing but couldn't do what a typing system does IMHO.

We are a small start up with a few programmers. When we started we seriously considered Clojure but instead we decided to go with Scala for its powerful typing system. We also use TypeScript whenever JS is needed.

I am sure many Clojure fans were in the same situation I was in. They love it and want to use it for serious work but have to choose other languages due to lack of type checking. To clarify, I don't mean Clojure isn't a language for serious work. I mean for some teams or companies, typing system is an important factor when choosing languages or tech-stack for serious projects.

I understand that typeless is a feature of Clojure. But TS proves that a sound typing system could add enormous value to the eco-system of a flexible and powerful typeless language.

P.s the other day I ran into a tech speech about Abstraction by Zach Tellman at a Clojure conference in 2017. I remembered he shared an opinion that in general Lisp language family, Clojure included, is too flexible which in someway an obstacle for it to be more successful.
TypeScript doesn't "prove" that a type system "adds enormous value" but I think the key point in your comment is "for _some_ teams... typing system is an important factor". And for teams that value flexibility instead, a language like Clojure is going to be a far better fit than Scala.

Where I work, we tried introducing Scala and the type system was very off-putting to everyone so we switched to Clojure. That was nearly nine years ago and now we run forty-some online dating sites with millions of customers on an entirely Clojure backend and we find the power and flexibility it offers allows us to change and enhance the system very quickly. We use Spec heavily in production for input validation, as well as in testing for all sorts of things that some folks might rely on a type system for but it is more powerful since it can describe both shape _and_ behavior in ways that type systems cannot.
Thanks for sharing your insight.

Can I ask what Clojure web framework you are using ?

REST api service is a key part of our service. We were a big fan of Netty and used to run a custom built REST api framework on Netty before we started our own shop. So when we started, we wanted to go with a Netty based web / REST framework. We evaluated Clojure's and found out most of Clojure web frameworks weren't actively maintained or lacked production cases. The one we were most interested in, aleph, seemed to be abandoned as well.

This was also part of the reason we went with Scala because of Akka http. But I am still keen to bring in Clojure for other projects for exactly the reasons you mentioned.

Some insight of your tech-stack would be very helpful. Thanks.
I'll add that clj-kondo has added a form of simplistic type checking recently which is worth checking out as well: https://github.com/borkdude/clj-kondo/blob/master/doc/types.md
And to address your comment, lots of people use Aleph in production successfully. So I'd consider it a safe bet.

That said, I believe the most actively maintained HTTP server with support for netty (and others) might be Pedestal: https://github.com/pedestal/pedestal

Otherwise, the most popular choice in Clojure which is actively maintained is going to be ring https://github.com/ring-clojure/ring with the bundled ring-jetty-adapter for using it with the Jetty server.

For further reading, I'd recommend you read over this guide: https://purelyfunctional.tv/mini-guide/clojure-web-servers/

Also keep in mind, you can use Netty straight up, the Java interop in Clojure is quite good, much much better then Scala's. So using the Java server interfaces directly isn't too bad a choice either.

Just like you said, we might go with Netty directly if we decide to start something with Clojure. We did some experiments with Java interop on top of Netty and all went smoothly. The bare metal approach would give us more flexibilities.
We don't use any "framework" with Clojure. We use a combination of libraries. Ring is core to almost everything. We use Compojure for routing in nearly all our apps (we use Bidi in one). We mostly use the embedded Jetty web server (via the "standard" Ring adapter) but all our apps can also start up http-kit instead, based on a command-line argument and/or an environment variable. We use Netty directly for one app that relies heavily on SocketIO stuff (via Java interop).

We use a lot of other libraries in different combinations across about a dozen services. A few of those are server-side rendered HTML -- we use Selmer for nearly all of the HTML rendering, with Hiccup used in one place for rendering an HTML fragment from Clojure data.

We originally looked at ClojureScript as a possible frontend back in 2015 but the ecosystem was very rough and the tooling seemed brittle (and, back then, the delta between Clojure and ClojureScript was a lot bigger than it is now). So we decided to build our customer-facing application using JS and React.js / Redux / Immutable.js etc. We have a frontend team dedicated to JS.

If we were doing the same project again today, I think we would evaluate ClojureScript again as a serious contender because the ecosystem has evolved dramatically in the last 4-5 years. I don't know whether we'd go with JS or cljs at that point.
Thanks. That's very helpful. We might look into Clojurescript as well.

By the way, can I suggest this forum's moderator to pin a thread to the top for  clojure developers with production experiences to share tech stacks and insights?

I think it would be very helpful for the community.
I'm not going to pin it, but there is a tech stack question at https://ask.clojure.org/index.php/8280/what-is-your-clojure-and-clojurescript-stack
Very helpful. Thanks
edited by
I think there is a misunderstanding here:
"static typing, which is extremely helpful for projects with more than a few developers"
"The Clojure core team has no plans to work on static typing for Clojure as we don't think it's necessary. Our effort is focused in spec (https://clojure.org/about/spec)"

AFAIU, and please correct me if I'm wrong, poster is asking for "Design time types", while the answer deals with static types as understood by Java.

What I, and, possibly, question author, are looking for is to write
(defn describe-dog [^:happy-puppy-co.api/dog dog]
      (println (str (:breed dog) (:age dog))))

and have intellisense pickup  :happy-puppy-co.api/dog spec and provide hints based on its keys and their respective subspecs. I.E. help me use ::dog as its author intended without having to go look into its file.

TypeScript does not exist post compilation, its output is JavaScript. The biggest benefit of TS is that it helps you understand other peoples code a lot faster (and provides some help with avoiding misuse too, but that is secondary).
It's not at all clear to me that your comment aligns with the original question, but if you are asking for specs integrated with function definitions, that is something we're looking at in spec 2.
Well, how else can the following be interpreted?

"I am ... fan of static typing, which is extremely helpful for projects with more than a few developers.

TypeScript ... also opens up lots of editor / ide tooling possibilities. I would say that TypeScript is the best thing ever happened to JS."

 AFAIU, this means constantly having to work with a lot of code that neither you, nor even your team wrote or reviewed previously. It's typical for enterprise apps to have dozens of dtos with 30+ keys and very inconsistent naming. Typical thought-process goes like "Ok, our function will get a 'sales-deal' dto, I need to convert it to 'financing-deal' and sent to analytics, how was our profit margin property called on 'sales-deal'? Was it 'margin' or 'markup' or just plain 'rate'? And was it a decimal already or did sales-deals still have it as itemized rate components sub-dto?" And then the same set of questions for 'financing-deal'...

I totally second the "best thing that happened to JS part". Sadly, trying ClojureScript feels like going back to JS - not good. I appreciate the power of spec, and how it helps me at runtime and testing. What I would like to do now is take all that power and plug it into my IDE to also help me with writing code. There is nothing to run or test if I haven't written it yet.
P.S. To better illustrate, what I'm looking for, check this library: https://github.com/vriad/zod
It lets you define spec-like schemas that are also understood and usable by TS compiler during design/compile time.

// spec definition - exists after TS compilation and can be used to verify schema
const dogSchema = z.object({
  name: z.string(),
  neutered: z.boolean(),

// verify schema in runtime
const cujo = dogSchema.parse({
  name: 'Cujo',
  neutered: true,
}); // passes, returns Dog

//TypeScript type definition - does not exist after TS compilation
type Dog = z.infer<typeof dogSchema>;
equivalent to:
type Dog = {
  neutered: boolean;

// use inferred type for compile-time type-checking and design-time intellisense
const fido: Dog = {
  name: 'Fido',
}; // TypeError: missing required property `neutered`