What does HackerNews think of core.typed?

An optional type system for Clojure

Language: Clojure

There's no Typed Clojure, still? I seem to remember people porting Typed Racket to Clojure; a quick search gives me core.typed[1] and typedclojure[2]. There's an example[3] looking like this:

    (t/ann hello-world-error [t/Int :-> t/Str])
    (defn hello-world-error [a] (inc a) ;#"Simplified a bit")
which looks like Typed Racket, where you'd get:

    (: hello (Number . -> . String))
    (define (hello num) (add1 num))
(both definitions are rejected at compile time; in Racket, it works in the REPL, too, not sure about Clojure)

...and Typed Racket is a really powerful type system (see refinement types[4]). So, I thought it's just a matter of time for Clojure to get to that level of power and support. It should be much easier to do this to Clojure than to Ruby, given that you have a working example of how to do it well. So I'm really surprised Clojure isn't gradually typed by now, with most of the code being annotated and type-checked at compile time.

[1] https://github.com/clojure/core.typed

[2] https://github.com/typedclojure/typedclojure

[3] https://github.com/typedclojure/typedclojure/blob/main/examp...

[4] https://docs.racket-lang.org/ts-reference/Experimental_Featu...

Do you know about the Typed Clojure project? More or less Racket's contract system, for Clojure:


To me, it's one of the great testaments to the power of Lisp that you can bolt on a static type system after the fact.

This is a great article, and I think does a great job of surveying the landscape as it stands. Particularly great to hear reports of ease in hiring and training, which doesn't surprise me, but is nice to be able to cite when risk-averse tech leads express concern about staffing and team-scaling.

I find it somewhat funny that a lot of the critiques here are about purity or types. I really see it as Clojure's core strength that it's both pragmatic and approachable. I'm a math/alegbra nerd, so I love languages like Haskell & OCaml, but they're much more austere for the uninitiated, and have had their own troubles growing. From my experience, the approach of Clojure spec is really nice because it gives you more flexibility/freedom/support in both thinking at a high level about the design of your system (in spec code) before you actually write the (implementation) code itself, while also not requiring it, and letting you add after the fact. You also get a lot for it: generative testing, destructuring, documentation, etc.

That having been said, I'm surprised that more of the pro-type folks here don't seem to be familiar with the [typed clojure](https://github.com/clojure/core.typed) project, which adds optional typing, along the lines of Racket's contract system. To me, it's one of the great testaments to the power of Lisp that you can fairly easily implement types on top of the core language.

My one beef with this article is that scripting with Clojure is now not only possible, but a joy, thanks to GraalVM and [babashka](https://github.com/babashka/babashka)! This has made a HUGE difference in the surface area of problems to which I'm able to apply Clojure.

Clojure has an optional type system: https://github.com/clojure/core.typed

(note that Lisp is flexible enough that a type system can be written as a library)

There's also this for run-time checking (i.e., you won't get any help at compile-time, but functions called with bad values will fail in very clear ways): https://github.com/plumatic/schema

What do you mean by Haskell-like type annotations?

Do you mean literally the syntax

    foo :: Type0 -> Type1 -> Type2
? Or do you mean the ability to have the type annotation on a separate line from the function implementation itself (rather than e.g. annotating its arguments individually)?

In the case of the former you have Hackett (https://lexi-lambda.github.io/hackett/reference-syntactic-fo...) which unfortunately I think isn't seeing too much work on it anymore.

In the latter case something like Typed Clojure may work (https://github.com/clojure/core.typed).

> - its dynamic-ness makes it not suited to large projects IMO.

Clojure can be typed https://github.com/clojure/core.typed/

> - The currently-accepted attempt at a non-type-system-type-system 'spec' is not well thought out.

Spec is not a substitute for a type system. It is much more loose, since you are just dealing with implicit predicates but at the same time it is more powerful than what mainstream type systems give you in terms of validity and expressiveness.

To add another point to the Common lisp over Clojure argument, DECLARE[0] offers a way to take advantage of type declarations natively.

I stopped using Clojure and don't consider it for new projects because I think types are invaluable documentation now, and it pains me that clojure and it's community doesn't believe the same way (typed clojure[1] does exist but it's contentious).

[0]: http://clhs.lisp.se/Body/s_declar.htm

[1]: https://github.com/clojure/core.typed

In all my time in Clojure I've never seen a project use compile-time type checking. core.typed [1] exists but I've never used it. Most people use either no type checking or if needed they'll judiciously apply something like schema [2] or spec [3].

[1]: https://github.com/clojure/core.typed

[2]: https://github.com/plumatic/schema

[3]: https://clojure.org/about/spec

Theres the new clojure.spec[1] in 1.9 and core.typed[2] adds an optional type system as well.

[1]: http://clojure.org/about/spec [1]: https://github.com/clojure/core.typed

The REPL-driven development of Lisps makes types not as important because everything is interactively tested as you write it. Having an optional type system makes it much easier to start with dynamic types and gradually add type annotations as your architecture stabilizes.

There's Typed Racket [1] and for Clojure there's core.typed [2]which do pretty much that. For Clojure there's also Schema [3], which is a bit lighter weight (it's not a full type system), but still gets you some of the benefits like validation and documentation.

[1] https://docs.racket-lang.org/ts-guide/ [2] https://github.com/clojure/core.typed [3] https://github.com/plumatic/schema

another one for the gradual typing would be core.typed https://github.com/clojure/core.typed
parentheses, at least in my exp, just fade away after a while.. there's core.typed which is static typing for clojure [0]

[0] https://github.com/clojure/core.typed

Clojure supports strong typing if you want it:



These are both libraries that you can choose to use. Both Typed Clojure and Schema are more powerful than Java's type system. By powerful I mean you can declare types and constraints that aren't expressible in most type systems (eg: an object can not be NULL, or a Map or Array must have specific keys or type'd values). Schema is run-time (we only leverage it in development and testing), while Typed Clojure is more akin to compile time.

Things that also get my goat (from a less type theoretic point of view):

- Assuming that type checking is the only thing you might want to do with your program at compile time. There are, in fact, arbitrarily many things you might want to do at compile time. Textual preprocessing, expansion macros, type checking, C++ template metaprogramming, etc. are all obvious examples. The fact that most languages bolt all of these things on as one-off features is a sign that something deeper is being missed (perhaps the 3-Lisp[1] idea, for example, or just consider plain ol' Lisp macros).

- Assuming that totality, soundness, and lack of side effects are always type checking virtues. Sometimes I'd like a "typechecker" that isn't always guaranteed to terminate. Sometimes I'd like my typechecker to launch some missiles. This is really more about the previous point.

- Assuming that type checking is NOT desired at runtime. Sometimes it is. Clojure's core.typed has the property that it is "just code" and can therefore be run at anytime and used for things other than checking that your code is safe to ship. [2]

- Assuming that a program that fails type checking shouldn't be run. Maybe it should and the parts of the program that fail type checking should be run inside some monadic context. Maybe we want not only gradual typing, but gradual enforcement. Maybe if we had richer type data, then "failing type checking" wouldn't be binary. Perhaps a program that fails certain type checks has to add runtime support to enable certain features, but if the same program type checked to be about pure memory locations, the runtime would vanish and a "straight to the metal" program would remain. (In other words, maybe what we currently call a language's "runtime" should just be a residue of things that couldn't be checked at compile time, among other things.)

- Assuming that types and the type checker should be a fixed feature of the language. Perhaps type checking should be something that is built as a library from language primitives. Take this far enough and you get something like exotypes. [3] Which reminds me...

- Assuming that "type" is the only kind of metadata you might like to attach to a symbol, expression, or value. There are many other things I might like to express, and I might like to use the usual machinery of type checking to analyze or work with those things. Clojure's value-level metadata and Common Lisp's property lists are some good examples of generalizing this.

- Focusing on manifest typing for verifiability of programs and ignoring the extra information it gives tooling. Visual Studio's IntelliSense and F#'s type providers are examples. At least one person at Microsoft has trouble sleeping at night because he or she is worrying about when the rest of us are going to figure this one out. [4]

- Assuming that most programmers want one or the other. The article touched on this, but I wanted to expound a bit. I would like my original prototype to feel like play-doh and my finished product to feel like concrete. Why do I have to do this in two different languages? "Oh I'll prototype it in python and then switch to Haskell when I want it to be safe." What?

- In general, assuming that it is the job of the language designer to provide the safety nets, rather than the DSL or API designer. A suitably designed programming language is one that could prevent operation Foo in layer 3 while allowing it in layers 1 and 2. This is one reason why languages like Idris are so complicated. Dependent types HAVE to be complicated because the language designer feels that it is his or her job to make sure the part of your program that runs at compile time terminates and doesn't do anything "bad". Why do we rarely question this assumption?

[1] http://lisp-univ-etc.blogspot.com/2012/04/lisp-hackers-pasca...

[2] https://github.com/clojure/core.typed

[3] http://dl.acm.org/citation.cfm?id=2594307

[4] http://blogs.msdn.com/b/dsyme/archive/2013/01/30/twelve-type...

https://github.com/clojure/core.typed has your optional typing. No suggestions for python syntax besides macros. Lots and lots of macros. :)
Well clojure having a lisp syntax allows an optional type system to be build as a library.


Has your friend used core.typed[0]? I haven't really, but I've wondered if it might be the perfect balance for people who really miss their type system.

[0]: https://github.com/clojure/core.typed

All of Racket's `#lang` family of languages are implemented with macros on top of core Racket. For example, Scribble, mentioned in another comment, is the documentation language used to author the article, and the Racket docs[1]). Or Slideshow, Racket's programmatic Powerpoint alternative.

Try to add a static type system to Racket or Clojure. Oh but you don't have the ability to change the runtime system. With macros you can do it. See Typed Racket[2] or Typed Clojure[3].

[1]: http://docs.racket-lang.org/ [2]: https://github.com/plt/racket/tree/master/pkgs/typed-racket-... [3]: https://github.com/clojure/core.typed

Most of the complaints in this article boil down to "macros are too powerful." I think this is the key part of the argument:

"A smart programmer is not necessarily an empathetic language designer; they are occupations that require different skillsets. Giving any programmer on your team the ability to arbitrarily extend the compiler can lead to a bevy of strange syntax and hard-to-debug idiosyncrasies."

There are at least 2 counter-arguments to this:

1.) in the simplest case, just restrict the use of macros. A team can easily adapt the rule that only the most experienced engineer on the team is allowed to write or approve macros. (And in my experience, the need for macros is fairly rare. I think my ratio is something like 100 or 200 normal functions for every macro that I write.)

2.) macros allow all kinds of interesting type checking, and data structure validation, and therefore they make it surprisingly easy to validate data and types as your data and/or vars get passed around your system. Consider all of these very interesting tools you can use in the world of Clojure:

Prismatic Schema which allows validation that a data structure matches a schema of (possibly nested) types:


and this now offers coercion, which makes this fantastic for importing JSON from other sub-systems or outside vendors:


(I assume you could easily validate before giving data to Liberator to export your data while conforming to your schema: http://clojure-liberator.github.io/liberator/ )

There is work being done on an optional type system:


Much effort has been made to make contract programming easy in Clojure:


But also I find the built-in syntax for writing pre and post assertions is clean and easy to use:


In short, there are an abundance of mechanisms available with Clojure which help facilitate the enforcement of any kind of schema or contract, and some of these tools are enabled (and their syntax is made clean) thanks to macros.

In short: macros can be used for evil, but they can also be used for good. They are very powerful, so everyone should be judicious about their use, but there is no reason to argue that macros render a Lisp unfit for programming in the large.

Having said all that, I'll remind everyone that the ultimate counter-argument is offered by Paul Graham, in his essay "Beating the averages":


If that essay does not convince you of the value of macros/lisp, then nothing will.

> but until you get some optional typing

The schema library form the prismatic guys should be what you want, its pretty powerful.


Aria Haghighi - Prismatic's Schema for Server and Client-Side Data Shape Declaration and Validation (http://www.youtube.com/watch?v=o_jtwIs2Ot8&list=PLZdCLR02grL...)

For static type checking: https://github.com/clojure/core.typed

> possibly another syntax baked in

Not needed in my mind. I would rather have less devs then C syntax. Not trying to be elitist but clojure will never not be a lisp, and if somebody can move from a(b) to (a b) then let him do python.

About this:

"until you get some optional typing"

There is work being done on an optional type system:


There are also some interesting experiments in enforcing specific data structures:


Much effort has been made to make contract programming easy in Clojure:


And if you would feel the urge to respond with something like "why is such important functionality in a library", I'll point out enforcing pre and post conditions (on a function) has a nice syntax that is part of the language:


I find that every time I read an article about Scala I am left wondering "Why don't these people just use Clojure?"

There's also TypedClojure (https://github.com/clojure/core.typed) which is based on Typed Racket (is what I read in previous discussions about it here).
JSON was used in the original article as a placeholder for Clojure forms. You can (optionally) get strong typing using Clojure records[1] (see defrecord) or using core.typed[2]. Datomic also lets you specify types[3].

I'm not "coming back" to you, as I don't appreciate your snarky, know-it-all tone. I'm replying for the benefit of others reading who may not be familiar with Clojure.

[1] http://clojure.org/datatypes

[2] https://github.com/clojure/core.typed

[3] http://docs.datomic.com/schema.html

Also, there's https://github.com/clojure/core.typed.

I don't have much experience with static typing outside of basic Java interop, but I gave it a shot on a recent project and it was pretty cool once I got the ball rolling and it caught my first actual bug.

> optional static typing


> multimethods

http://clojure.org/multimethods - also, arguably better for many use cases: http://clojure.org/protocols

> native and efficient executables

Clojure performs much better than popular web development language implementations like Python, Ruby or Node.js. See http://www.techempower.com/benchmarks/ People have found it performant on harder problems: http://clojurefun.wordpress.com/2013/03/07/achieving-awesome... And it's possible to produce executables that depend only on the JVM, which is just as easy as fully-native for deployment.

I grant you the one about the Common Lisp condition system. I wasn't familiar, but reading about it (http://c2.com/cgi/wiki?CommonLispConditionSystem), it does look intriguing. Apparently something inspired by it is available as a Clojure library, but the last commit was 10 months ago, and I hadn't heard of it till now: https://github.com/scgilardi/slingshot

Not trying to sell you anything, just trying to help you find some things you've been missing:

a) https://github.com/clojure/core.match

b1) https://github.com/clojure/core.typed

b2) I agree that Clojure's facilities for mixins is under-utilized and that it lacks a proper facility for delegation (ie implicit conversions in Scala), but see (doc extend) for how awesome "traits as data" can be. Here's an example: https://github.com/stuartsierra/clojure.walk2/blob/2250e04c7...

c1) I hate generated documentation, but I understand why it's necessary for Scala. When I did some scala programming, I was so pleased with ScalaDoc, since it really helps you navigate that complex scala.collections hierarchy. Feels like having a pretty decent substitute to a good IDE. In Clojure, I don't feel the need nearly as much... Also, I quite like reflective documentation with the doc macro.

c2) Already covered interfaces/traits in b2

For a and b you should check our core.match [1] and core.typed [2]. One of the best parts of using lisps is that things that are features of other languages can be implemented as libraries. Not to say Scala probably isn't great too, but I haven't ever really used it.

[1] https://github.com/clojure/core.match

[2] https://github.com/clojure/core.typed

optional clojure type checking: https://github.com/clojure/core.typed/ (not finished, but showing promise)
There's some progress being made on static typing in Clojure, too: https://github.com/clojure/core.typed

Still fairly early days on that though (missing protocols and rest parameters are the big issues I think)