(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...
https://github.com/clojure/core.typed
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.
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.
(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
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).
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.
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).
[1]: https://github.com/clojure/core.typed
[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.
[1] https://docs.racket-lang.org/ts-guide/ [2] https://github.com/clojure/core.typed [3] https://github.com/plumatic/schema
https://github.com/Prismatic/schema
https://github.com/clojure/core.typed
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.
- 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...
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
"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:
https://github.com/prismatic/schema
and this now offers coercion, which makes this fantastic for importing JSON from other sub-systems or outside vendors:
http://blog.getprismatic.com/blog/2014/1/4/schema-020-back-w...
(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:
https://github.com/clojure/core.typed
Much effort has been made to make contract programming easy in Clojure:
https://github.com/clojure/core.contracts
But also I find the built-in syntax for writing pre and post assertions is clean and easy to use:
http://blog.fogus.me/2009/12/21/clojures-pre-and-post/
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":
http://www.paulgraham.com/avg.html
If that essay does not convince you of the value of macros/lisp, then nothing will.
The schema library form the prismatic guys should be what you want, its pretty powerful.
https://github.com/prismatic/schema
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.
"until you get some optional typing"
There is work being done on an optional type system:
https://github.com/clojure/core.typed
There are also some interesting experiments in enforcing specific data structures:
https://github.com/prismatic/schema
Much effort has been made to make contract programming easy in Clojure:
https://github.com/clojure/core.contracts
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:
http://blog.fogus.me/2009/12/21/clojures-pre-and-post/
I find that every time I read an article about Scala I am left wondering "Why don't these people just use Clojure?"
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
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.
https://github.com/clojure/core.typed
> 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
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
Still fairly early days on that though (missing protocols and rest parameters are the big issues I think)