I have switched between many programming languages, and if you are considering Clojure I would highly recommend it. It is immutable first, which helps eliminate many classes of explicit bugs and non-obvious anti-patterns. It also works across Java / Javascript allowing for a common code base of business logic across all layers, and with React Native looks and performs well on iOS and Android.
Clojure uses reader conditionals https://clojure.org/guides/reader_conditionals that allow writing code that cross-compiles between different platforms, and generates platform specific code when necessary.
The perfect language to expand your brain. In a world dominated by java, php, c++,... In my opinion yes, Clojure could the perfect language to continue learning concepts, at same level would be Haskell.
I personally don't view Scala as a FP language, it's a FP/OO hybrid. It's not opinionated in that regard, and a lot of code in it is written in traditional OO style or a mix of the two.
Erlang is another widely used functional language, but it's not very widely used outside the telecom niche. Clojure has the advantage of running on the JVM, and makes it easy to target a much wider range of domains.
Clojure is a lot more focused and opinionated. It's unarguably a functional language first and foremost. Note that you can write any style of code in any Turing complete language, the key difference is what style of code the language encourages.
Clojure is also a hybrid. The other part is called Java or Javascript. This is claimed as an advantage, since there is already a lot of functionality in another language to be reused by a direct interface. Some stuff is simply deferred to the host language and since the other part is based on OOP, applications will then be a mix of more FP-oriented code calling OOP-oriented code.
This is an implementation detail, and doesn't affect the API presented to the user. For example, consider the clj-http library that's backed by the Apache http Java library internally https://github.com/dakrone/clj-http . As a user of clj-http, I don't know or care about the fact that it's a wrapper around OOP code.
Furthermore, if you wanted to, you could reimplement the internals in Clojure and the users of the library wouldn't be affected.
Being able to leverage existing mature libraries is a huge advantage, and the fact that they can be used via idiomatic functional APIs provides the best of both worlds experience.
To fully explain, it would require a blog post, and it has been covered before, but i'll make a try:
- Proper stack traces. Debugging Common Lisp code is not only easy, it is far easier than in 90% of programming platforms out there. Clojure is severly lacking here; it has been promised that there will be an improvement; i will be watching since this will greatly improve Clojure
- "Lisp-2 versus Lisp-1": On Common Lisp, functions and variables have separate namespaces so they don't conflict. This makes writing code, especially macros much easier. Macros are one of the biggest reasons to use a Lisp dialect in the first place, so this is a rather strong point.
- Object orientation facilities: CLOS, CL Object System, is arguably the most powerful OOP system on a mainstream language. You should really take a look at it, it can just blow your mind. Clojure has no comparable OOP implementation. I recommend CLOS to anyone that feels disillusioned with OOP.
I could stop here, because the three points above are very strong differences, but there are some other points to consider:
- Standarization and portability (1/2): CL is an ANSI standard, and there are many ANSI-compliant, mature, compilers: LispWorks, SBCL, Allegro CL, ABCL, Clozure CL, ECL, CLISP, and others. There are features that the ANSI standard does not cover, like interacing with C libraries, but there are already portable libraries that allow you to also do this on a portable way. So, in this way, your code can be compiled by those implementations, often without any change needed at all.
- Standarization and portability (2/2): The ABCL compiler allows you to target the JVM and call Java code from Lisp or call Lisp code from Java. The Clozure compiler lets you target the LLVM. Practically most modern platforms can be compiled from CL to native code by using the suitable compiler.
- Interfacing: As mentioned above, you can interface with Java, and with C, as needed. C interfacing is very easy, btw.
- Speed: CL can be very fast. For example SBCL is really fast, particularly with numbers where it can be as fast as C. In general it is in the same speed of Java code running under the Oracle's latest JVM. I think that CL code under SBCL probably should be much faster than Clojure running under JVM.
- Number data types: CL supports, with high perfomance and natively: Complex numbers, Rational (fractional) numbers, floating numbers (with the IEEE standard), integers, binary numbers, and arbitrary-length numbers. Bonus: The same typical operators (+,-,*,/) work perfectly with them, so using them is simple. In this feature, CL fares even better than Fortran (seriously.)
- Static typing? CL is not statically typed, but the standard allows you to declare the type of your variables. A compiler like SBCL will catch many type errors during compilation (and of course they will also be catched at runtime). Doing this has also the big bonus of increasing performance dramatically.
- Mainstream: While not popular, Common Lisp is a mainstream language, so there are tons of books and resources out there, as well as having been used successfully complex production systems, like for example auto-piloting the Deep Space 1 spaceship (NASA) for days.
The perfect language to expand your brain is mathematics.
I agree with Wadler that the best programming languages are discovered. First, the principles are revealed in a proof by a logician. Then about two or three decades later computer scientists "discover" the same principles. And another two or three decades after that mainstream programming languages reluctantly integrate the ideas.
This is all to say that computer languages are behind the curve. If your goal is to expand your brain you're better off digging into the fundamentals behind computer languages. Then you can see the flaws and trade-offs in all languages including Clojure, Haskell, C, Agda, Idris, Javascript, etc, etc, etc.
I've said the Clojure is a decent language. I don't think there is a perfect one. Not yet.
I just saw an article recently about core.async and the "go" macro, which seems to effectively give you goroutines in Clojure. I'd be very tempted to use it for that reason alone. I love Go's concurrency, but it can be a little on the verbose side. Good for some projects, but not all of them.
Go is verbose? I'm surprised to hear that, to be honest. Can you say what it's verbose in comparison to? What production language/framework has terser concurrency with the same expressiveness?
Clojure for one! But I thought it was a common complaint that Go's lack of support for generics and other language design decisions aimed at keeping things easy make it much more verbose.
That's just a non-user's perspective on the scuttlebutt, though - I don't have any actual experience with Go.
I didn't mean the concurrency, just the language in general. And verbosity isn't always a bad thing. I can dive into pretty much any Go codebase and get a good idea of how it works pretty quickly, and that's been a pretty unique experience for me.
If you are curious about core.async and want to learn more you can have a look at the post I wrote on it-- [http://abhirag.in/articles/batman_clojure.html]. I wrote it while trying to learn it myself so not the most advanced resource but I hope somebody finds it helpful :)
Note -- The page is kinda resource intensive because it contains a running clojurescript interpreter for interactive code snippets.
I'm an EE who is getting more and more interested in software development and computation in general. I've been reading Paul Graham and Peter Norvig and decided to learn Scheme. Any thoughts on Scheme vs. Clojure for learning?
They are both LISP dialects, therefore worth learning. Clojure(Script) is nice because it has great interoperability with underlying platforms, so it's also a very practical and useful language for the real world. But if you started Scheme stick with that first - focus.
To a first order of approximation, for this task it doesn't matter. Both are good options. I do recommend reading SICP while at it; using Scheme for SICP is easier than using Clojure.
On the other hand, Clojure is more production-ready than most Schemes, in my experience -- this sentence being one of the reasons as the Scheme landscape is very fragmented, despite the standard(s). For example, in my own SICP journey (http://eli.thegreenplace.net/tag/sicp) I was using PLT Scheme, one of the most usable implementation back in the day. Unfortunately it forked off into a slightly different language - Racket - since then. So YMMV
Clojure is definitely more practical, being much more widely used in production and running on the JVM (which provides access to a huge array of Java libs).
Scheme is simpler though, and probably more approachable for a beginner. The hosted nature of Clojure means you'll need a decent understanding of the underlying JVM to understand errors--the first time you get a stack trace you're going to seriously question your choice. Also Clojure's syntax is more complicated (but really, not at all complicated compared to Java, C#, Python, etc.).
You should be able to pick up Clojure pretty quickly if you're already familiar with Scheme, the programming style is pretty similar.
I have to say after diving into ClojureScript I came away thinking differently about a number of things - state management, how to program functionally when you are more constrained than in more permissive languages. It was a real learning experience.
This ^ right here, is what gets lost in all the squabbling over functional purity and parenthesis hate.
For a huge number of developers coming from the Javascript/web side of things, Clojurescript is poised so perfectly to usher them into thinking about composing your programs as pure functions, immutable data structures and organizing your state such that it is easy to reason about.
Eventually, Clojure led me to Haskell, and even back to how Javascript ought to be written (i.e. fantasy-land specs). This has been the single most eye opening learning effort that I have ever had the joy of undertaking since I started programming.
Clojure is the language where LISP clicked for me. Can't recommend it enough, it's got all of it - immutable datastructures, convenient data literals, simple and composable concurrency primitives, very thin interface to host VM.
Just put enough effort to get beyond that "omg parenthesis" barrier and it will be a delight. It's like that Half-life joke: there are two kinds of people, those that finished Half-life many times and those that never got off the train (which as somebody will probably point out, is a variation on another joke, but you get the idea).
I've learned to love the parentheses, because they embody what is so nice about lisps (and particularly Clojure)--inside the parentheses is a little nugget of functionality that generally stands on its own. No syntactic tricks, the only "trick" is the macro system, but it's so straightforward that I'm not sure I'd call it a "trick".
The other immediate barrier is when Clojure barfs an ugly stack trace. I've heard that 1.9 will address that with core.spec, but I haven't explored it yet.
hmm sexy idea to have spec help for stacktrace. I expect something cute like elm error messages.
About the parens, it's true it's one great thing, every construct has a value [1].
It's funny because You hear about object orientation where things are uncoupled and standalone yet the language isn't this way. Lisp has this, it's the incarnation of its own idea. But it looks silly if you're in it for the syntactic appeal.
[1] Although I often wish for a helper thing when evaluating the body of, say a let to grab the bindings instead of saying "unknown var a in (inc a)". This is lisp in general
stack traces still suck as far as I've seen on the 1.9 alpha branch. And while clojure spec is pretty neat, the barge-load of information it dumps on a spec failure is overwhelming.
That said, the info it dumps is in the form of clojure data structures. In my own programs, I've added code to refine this output to something useable, but it's particular to my project and I don't think generally useful.
There are other projects to aid in this fashion too. Expound is one that takes inspiration from Elm, and Inspectable is intended to help at the repl by launching a gui for exploring Specs and failures.
I've tried both, but couldn't get inspectable to work in my environment, and expound isn't meant to handle the error I've been dealing with, so I can't speak to either.
Expound recently made my life a lot better by cutting through one of those ridiculous spec errors and telling me exactly what was wrong! I was really impressed.
"expound isn't meant to handle the error I've been dealing with"
If you have some time, would you be willing to expand on this? I'm curious to learn more about your error. I'm the author of Expound so I'd like to make it more useful (or, at least, understand the limits of a library like Expound).
There are many things to love in Clojure, like core.async, the lispyness, etc. But for me, their biggest breakthrough is with no doubt the data-structures.
Clojure default of immutable hash maps and vectors was really bold. They had to innovate by giving a twist to Bagwell's great research on data-structures, and they managed to build what in my opinion are the first set of general data-structures suitable of representing traditional hierarchical object-tree data-models immutably, at the scale of large interactive software.
Without these, the single-atom architecture that now is popular as ever with Redux, Elm, etc. would not be possible. The flourishing of functional programming for interactive software that followed shows to me that the biggest impediment before was not the lack of functional ways of expressing interactions (the Haskell people have devoted a lot of notable effort in the FRP camp). Instead, just having efficient data-structures to write persistent document models, escaping the rigidity of lists and rb-trees, allows you to write big interactive sofware in a functional manner even with the most dumb abstractions (which is a good thing, KISS!) and even stupid language (JavaScript!).
In the past I also did some other work on bringing Clojure Transducers to C++ (https://www.youtube.com/watch?v=vohGJjGxtJQ&t=1614s) but I know consider it a much less important effort---without the data-structures the other parts of Clojure feel almost like just sugar.
Any book on clojure and website too will go over them. They are fundamental to using clojure.
I don't know enough about haskell to say what it does or doesn't have in relation to clojure. However, it's not simply the data structures, but how the language is built to interact with them from a programmers perspective.
From a user interface to the language point of view, I find the ways of interacting with maps to be very nice.
In particular the dual purpose of "keywords" to serve both as a good index/key in said hash maps, but also as callable look up function for the same key in a hash map leads to simplified code.
This, coupled with the extremely high usage of those maps in typical clojure code, makes a big difference.
To expand on that a little, "keywords" are symbols that begin with a colon, which I believe is the same as common lisp. :for :example :this :is :a :sentence :of :keywords. They are commonly used as the key in a map. Which has a literal syntax like (def map1 {:a 1 :b 2 :c "taco"}). To pull a value out, you can evaluate (:c map1) which will return "taco".
They are hash maps based on work by Philip Bagwell, and I think a lot of languages have versions of this now. At least scheme and Common Lisp do. But I don't believe they have the nice literal syntax and interaction syntax.
Many Lisp traditionalists are bothered by the use of curly braces for maps and square brackets for vectors, however I'm a fan. Without this bit of syntax, you have to add verbosity to the language to differentiate defining a map from doing a lookup, etc.
For resources, Clojure for the Brave and True is pretty popular. Also the online community is quite friendly, although I have noticed some trends toward "the one true way" of doing things that puts me off a little.
>Many Lisp traditionalists are bothered by the use of curly braces for maps and square brackets for vectors
I have definitely seen this. I'm not sure that I understand this sentiment. The only thing that Clojure is missing is the "system" feel that Common Lisp provides, with things like character macros, symbol macros, etc.
That being said, I definitely appreciate how thought through Clojure is, especially when compared to CL. Several things are missing/don't feel well thought out in CL (notion of a `calleable` type is gone, `nth` isn't defined on vectors or strings (but you can still `length` them!) nor is `first` and `rest`) That provides a great deal of friction.
You can do this using the metaobject protocol and defining a class with a metaclass of FUNCALLABLE-STANDARD-CLASS, for example, given:
(defclass c ()
((x :initarg :x :accessor x))
(:metaclass funcallable-standard-class))
(defmethod initialize-instance :after ((c c) &key)
(with-slots (x) c
(set-funcallable-instance-function
c
#'(lambda ()
(format t "~&I'm #~a" x)))))
Then (funcall (make-instance 'c :x 3)) will print "I'm #3".
> `nth` isn't defined on vectors or strings (but you can still `length` them!)
ELT in Common Lisp is generic over sequence types. NTH is specific to lists. If you want to write a function that is generic over sequences, use ELT. If you know that you have a list or an array, and you're writing something performance sensitive, you can avoid type dispatch overhead by using NTH or AREF instead of ELT.
> nor is `first` and `rest`
You can avoid that friction a lot of the time by writing in terms of MAP, REDUCE, REMOVE-IF-NOT, and so on, as well as LENGTH and ELT, which are all generic over sequences, or doing (iter (for x in-sequence sequence) ...) or whatever.
But if you really want to use FIRST and REST to walk through a vector, you can do (coerce vector 'list) first to convert it. Coercing a list to list will just return that list, so that version would work for either, too. I admit, there's no real reason I know of (aside from lack of demand) that there isn't a predefined function like:
Common Lisp had a lot thought put into it, but the design goals were different. One of the design goals was to support efficient compilation given various constraining dimensions: the need for speedy compilers vs. the need for delivery of applications on standard hardware. Thus a Lisp compiler should be able to generate fast code, even by using whole-program compilation. Thus the language has to take care of a wide range of possible implementations, not just one on a particular virtual machine.
Clojure OTOH has a relatively straight-forward compiler written in Java, only targetting the JVM in a standard way. The Java/JVM infrastructure for code generation (JIT), low-level optimizations (JIT), memory management, application building and delivery could be reused. With all its features and limitations (for example no TCO, fixed JVM instruction set, ...).
Common Lisp also not only provides some generic operators, but also lots of non-generic operators. One reason for that is the will for backwards compatibility (which was another goal in the Common Lisp design) and the idea that something like Common Lisp is also a low-level languages or provides at least a layer of low-level stuff, functions you would use to implement higher-level stuff.
Common Lisp is the next step in the evolution of Lisp I to Lisp 1.5 to Maclisp to Zetalisp. Thus it contains much of the core Lisp functions, stuff you can read about it the Lisp manuals from 1958 and 1960. People did not want to throw their code away, they wanted a modernized Maclisp. Thus one could port a 100kloc Lisp program from Maclisp or Zetalisp to Common Lisp without too much effort. The main operations were there.
Clojure OTOH has almost no backwards compatibility to earlier Lisp dialects. Programs can not be ported from Lisp to Clojure in a useful way. They have to be rewritten. But that was okay, since the main target were not Lisp developers, but people who wanted a language with Lisp influence in the Java/JVM eco-system (or possibly other eco-systems too).
Thus Common Lisp looks to you like it has less thought put in, where people actually struggled to be backwards compatible and modernize the language at the same time.
So Common Lisp has lots of non-generic functions, a layer of generic non-extensible functions (like the sequence layer) and a mechanism for extensible generic functions (CLOS). Something like CLOS is implemented on-top of the non-generic Lisp, with a few parts using implementation specific mechanims. One does not need to use the primitives of a different host language, because Common Lisp is already capable of hosting with its primitives.
> Many Lisp traditionalists are bothered by the use of curly braces for maps and square brackets for vectors, however I'm a fan. Without this bit of syntax, you have to add verbosity to the language to differentiate defining a map from doing a lookup, etc.
I agree with you, in the sense that they are useful, however the complain of "Lisp traditionalists" will be that in any case, if you need them, you can add them to Common Lisp by using reader macros. So in CL the curly braces and brackets are "free" to be used (i.e. define what they should do) in any way you want; on Clojure they are already taken, so it feels a bit uncomfortable.
They are pretty amazing. Of course there are implementations in Haskell now, but the reason they are especially cool in Clojure is how well they integrate with the language and its other features.
Okasaki's work is really interesting, and it is quite mind-blowing the theoretical properties he achieves by exploiting laziness and so on. At the same time: those data-structures have one fundamental flaws they have high constant factors, specially outside of Haskell. That was Phil Bagwell + Rich Hickey revolutionary contribution in my opinion... finding data-structures that are rather simple yet but work well in practice because they make the right engineering trade-offs and work well with CPU caches, etc.
> In the past I also did some other work on bringing Clojure Transducers to C++ ... consider it a much less important effort---without the data-structures the other parts of Clojure feel almost like just sugar.
Very few clojure programmers consider Transducers very important even in Clojure. While they're convenient for implementing Clojure's stdlib and they have some nice properties, they're very difficult to work with. It takes a very experienced clojure programmer to use transducers. Heck, I know some famous clojure developers who admit they themselves don't feel confident using them.
But the main challenge for them is that transducers just scream for some basic type validation so that you don't accidentally chain together nonsense. C++ could provide that, and you'd end up with all the same properties. In particular, they offer something like half the monad laws and a more easily composed form of CPS (how your continuation is called is more predictable).
I'd think that might be a really valuable thing in C++, as it's a construction that reproduces stream fusion.
Know what you mean RE transducers. I still haven't come to a point where using the transducer pattern is more convenient than just (partial map f) tbh.
Yet to dare clojure spec but it feels likely that the next mind blowing journey will be therein.
Writing your own transducers is hard and error prone, but I think using and composing the ones given you by clojure.core is actually rather easy (Especially using the 3 arity of `into`).
I dunno. Except for one very specific project, I've yet to see a "Wow!" use case for transducers. The only place where they really shine is working with channels, but I think channels are a big design mistake (after extensive work trying to tame them).
Interesting! Why do you think they are a big design mistake? I can understand how they can be misused, but I find myself missing them in every language I used that doesn't have them...
So either I'd rather talk about the COMPUTATION and resource safety rather than the channel, as in Pipes/Conduit/Streaming from haskell.
OR, I'd rather deal with a direct actor model a la Erlang, Pony and Cloud Haskell.
Go's experience is so goddamn miserable it should just put the entire notion of async channels as a higher level programming library to bed. As a low-level primitive, it's okay but not fundamental.
Actually one of the things I found during my work on transducers is that actually they seem to me more implementable in C++ than in Haskell. For example, you can implement state-less transducers with almost zero overhead, something that in Clojure is definitely not true (so the standard transducers do hide state inside the closure of the reducing functions). It is also easier to do n-ary transducers for zipping and unzipping and everything, as you intuit, is type checked.
Still, I agree that transducers are most useful when you have libraries that make sense of them. For example, you could implement a lot of "Rx" using transducers. Ideally I see a user never writing a transducer, and occasionally passing one to some library that provides "higher order" reactive collections (channels, observables, etc.) that can be transformed with transducers. For example, my motivation to implement them in C++ was to build "observable/reactive lenses" (something like "cursors" in Om) [1] where you could use transducers to specify how you "zoom" into the virtual sequence of state values...
Anyways, they are an interesting coding tool, but they don't really influence the architecture of the application.
> they seem to me more implementable in C++ than in Haskell
If you don't mind the overhead, it's ContT wrapping some base monad. Or you could make it integral. That is more specific than transducers, I grant. But there's not much they can do that ContT over like Array does not.
I think it's too bad they were invented in Clojure, because they're brutally hard to use right and write in clojure.
I have some interesting work I could share in private on using them to provide stream fusion properties to pipelines of channels. Hit me up on keybase if you'd like to see. I don't want to open source it because then people will expect me to explain them and maintain them and I don't feel like it :)
Sorry, I actually meant "than in Clojure". You are right about the ContT. I'm actually curious about your transducer based stream fusion stuff... I'll hit you up later about it :-)
I keep hearing this sentiment, moreso than I've ever seen for any other language. Once you "get into" Clojure, you're both A.) Extremely productive with it, and B.) in love with Clojure.
How does it hold up for those of us that really love static typing and compilers?
the static typing part - yeah, you just have to try and let go. there is typed.clojure for gradual typing though last time i checked it was quite slow. clojure.spec is a great attempt at bringing some discipline and provides quite a bit more than your usual typing system can do, though not entirely on compilation level.
as for compiler part - you're in for a treat. clojure is a compiled language - everything compiles down to jvm bytecode. the surprise part is that due to homoiconicity you get a compiler that's happy to let you hook into particular pieces of your code and (since code is literally represented as usual clojure data structures - lists/maps/vectors/etc) modify it on the fly before compiling. you basically define `f(code) -> other_code` - the ultimate metaprogramming.
I love static typing. I really dislike almost every dynamic language I've ever worked with. But Clojure is currently my favorite language. I've done C#, F#, Go, JS, Ruby, Dart, C, C++, dabbled in Haskell, OCaml, purescript, D, Nim... (I like programming.) Anyway, once I dove into Clojure, I couldn't go back to any other language. I do wish it was more general purpose (not so slow to boot, had a decent native / non JVM, non JS target.)
If "expanding your brain" is really what we want to optimize, why not learn Haskell? Or for that matter, why not learn something even more strongly typed, like Agda or Idris? Or even a theorem prover like Lean?
Most of the reasons the author presents would either be expanded in one of those languages, or is immaterial to the goal of maximizing learning. After reading the article I have no more reason to consider learning Clojure over learning any of the languages above.
One huge difference is the workflow you have in Clojure. REPL driven development is an experience unique to Lisps. When you're working with Clojure, any code can be run in the context of the live app straight from the editor as you write it. This is an amazing experience and very mind expanding in what a development process can look like.
Also, anybody who wants to try FP style programming, but isn't interested in static typing is much better off with Clojure. Type systems in languages like Haskell and Idris add a lot of complexity and mental overhead that's not present in a dynamic language.
Yes, you can reload any symbol in the CLJ REPL directly from the editor. You can also evaluate any function within the context of the running application without going through the main. The REPL is an integral part of the development process in Clojure. You can see this in action here around 15 minute mark https://www.youtube.com/watch?v=nItR5rwP4mY
R and RStudio are actually quite good in this aspect.
- You can evaluate any part of editor in console with keyboard shortcut. The only difference between using console and editor is that your input is easily saved in editor.
- The environment browser make inspecting variables, data structures much easier.
Besides, in R you want to use vectorized functions for better performance, so you search for general functions and combine them, which actually promote good functional programming style instead of a big control block with many processes intertwined.
As an Emacs user, from my experience the Clojure REPL has much better integration with my editor than any non-LISP I've used, which includes Haskell and Erlang. Most of the time I'm not typing commands into the REPL - instead, I'm evaluating code inline, which means I don't need to jump back and forth from the source file to the REPL. It's a much more interactive and productive experience for me, and I haven't been able to fully replicate anywhere else - though it's possible I haven't discovered the right tools for those other languages yet.
When the experience is amplified by homoiconic data structures and structural editing, yes -- exploratory, ephemeral metaprogramming is a breeze. Compound Clojure data literals have a fully readable print representation that can be generically manipulated by built-in core library functions. Say I have a DSL that takes a bunch of nested maps and vectors as an input spec and compiles them to nested closures, and I want to create a large spec derived from some other source satisfying certain constraints, but it's not worth defining yet a higher-level DSL for a one-off definition that I intend to commit to source control. I can generate that data structure any way I like and spit the result out at the REPL or in an interactive buffer, then effortlessly plop that structure or any of its substructures into an EDN file or just inside a `def` or `let` binding in a regular Clojure file, which I then pass to the DSL compiler. It's much simpler than manipulating things like GADTs in Haskell. Even in Common Lisp, it might require some ad-hoc parsing code to accomplish the same thing.
I don't think that's entirely unique to Lisps, you can get a REPL into an existing Erlang instance very easily. More easily than getting nREPL running in Clojure, actually. I believe Smalltalk as well, although I'm less confident on that one as I haven't used it much.
The difference is in the editor integration. When you work with Lisp, your editor is connected to the running instance of the application. You have shortcuts to reload any symbol in the application at runtime directly from the editor.
As someone who's professionally developed mostly in Erlang and Clojure in the last 6 years, it's not even close. I love Erlang, but the REPL is severely underpowered and used mostly for debugging and inspection.
I work in Scala and it has live code update, interactive worksheets and a repl.
As for type systems increasing complexity I think that depends on the application. As a beginner to Clojure I would pass unstructured data around all over the place and then have the mental overhead of trying to remember the structure or fix it all at runtime.
The biggest difference is the editor integration. Scala, like most languages has a REPL that's basically a toy you use on the side. Clojure REPL is tightly integrated into your workflow.
I'm not aware of any non-Lisp languages that provide anything close to that.
The REPL driven workflow directly addresses the problem you're describing as well. When I'm working with Clojure, I never write a lot of code before running it. Each time I write a function, I run it and see exactly what it's doing. This means that I never have to keep a lot of context in my head.
Let's say I need to pull some data from the database, massage it, and return it to the client. I would write the function to read the data, run it to see what shape of the data I get. Then, I would write the function that consumes that data and transforms it. Again, I'd run it and see that I have the data I want, and so on. At each step of the process I only need to consider the last step and the next.
Also, Spec and Schema are commonly used in libraries to provide the description of the data at the API level. This is where I really care what the shape of the data is, as opposed to typing every single function I write.
Nice talk. I agree with you that the repl in Clojure and Common Lisp etc is much more of a first class citizen in the development environment. What people often don't realize is how powerful the repl and worksheet can be in scala development.
Thanks, having editor integration with the application runtime completely changed my workflow. I simply couldn't go back now, having instant feedback on the code you write is extremely satisfying.
What surprises me is that none of mainstream languages provide this kind of environment. Lisp and Smalltalk have been around for many decades, and somehow this workflow ended up being completely neglected in the mainstream.
This is what clojure.spec is meant to address. It's helped me, but I am actually trying to get some momentum in Haskell now... I have a saying, "Constraints free the mind." Frameworks, Type systems, etc., provide the constraints we need to let our mind think about the actual problem instead of suffering from analysis paralysis.
Absolutely agree. However there is a "perfect language" for every programmer to learn, depending on their background and spare bandwidth. For many programmers the jump from their primary language to Haskell or others is too high, not only in terms of complexity but also utility.
Several colleagues of mine are picking up new languages right now and a common criteria is they want to build something tangible/useful within a limited time. Having access to the JVM and third party libraries they already know is certainly a plus.
The type system is only one part of a programming language. You can, quite reasonably, have two languages with exactly the same type system that wind up with vastly different ways of programming them due to what you're given as primitives/the standard library.
Programs aren't usually directly portable between languages primarily for that reason - the language designers optimise for a specific way they want their language to be used, then everyone else follows, and eventually you're going against an entire ecosystem if you want to do something different.
Or Typed Racket if one wants typing and lisp. I don't know how core.typed compares to typed Racket, but that might also be enough to get the flavor of types?
I think people should learn both Haskell and Clojure :). However, while Haskell is very obvious in its differences with more commonly used languages, I feel that Clojure is more subtle.
I felt like an idiot while I was learning Haskell; but in Clojure I only realised my idiocy after perhaps a year of working in it. Clojure feels deceptively close to more standard languages, but in its own way is as different to them as Haskell. It just isn't as immediately obvious.
All the languages you've mentioned will also expand your brain, but in a different direction, mostly related to correctness and provability of specifications.
Clojure will expand it in different ways. Here's some examples:
Having spent some time programming in Clojure, for the purpose of expanding my brain, I suspect a better Lisp to start with might be one of the classics - Racket, Common Lisp, etc... One of the reasons I gave up on Clojure was the lack of documentation of libraries, the bad debugging experience (those stack traces!) and the fact that there's no avoiding the JVM. They're all distractions to the learning (that was 2 years ago, I'd be happy to hear that things have changed).
I think Clojure is a great language, but if you just want to expand your mind via a Lisp then I think you'll get more for your time with another implementation.
I had a similar experience, trying to learn clj/cljs before I know java, javascript or the web stack. I am a classic arrogant developer (I'll learn it all at once as I go) but in retrospect it was honestly a mistake. If I'd learned basic web servers+node+react/redux then Clojure I'd know more of both than doing it in reverse.
Common Lisp is much more complex and challenging. Also, it is not really a functional language in the way Clojure is; it offers mutation, OO and imperative programming as its normal paradigms.
I can occasionally get away with writing Clojure for "real" work, but for hobbying (and inshallah for real work someday) my goto is Racket. I feel like Racket is something of an optimum among the lisps in terms of brain expanding, modernity, tooling, libraries, docs, and community.
I tried Googling bcrypt; one C wrapper that hasn't been updated in 3 years pops for Racket, there's at least 3 recently updated libraries for Clojure. I'd suspect the library availability is gonna be similarly tilted in Clojure's favor in most domains. Clojure has lots of compelling and new stuff - Figwheel, re-frame, Neanderthal.
What's Racket's killer library that's doing something no one else is?
Not an expert, but I'm guessing it could only help through increasing adoption. If Racket was blazing fast, a lot more people would consider using it and more libraries would be built and maintained.
I don't think anyone will claim that any JVM language or node doesn't handily best Racket in terms of sheer numbers of libraries. What Racket does have are solid core libraries with good docs.
Also, it's not a library, but the whole #lang thing seems to be the coolest game in town for implementing DSLs and other more fully featured languages.
I agree. I read through Clojure for the Brave and True, and played with it a bit, but it was too much new stuff at once coming from an only Python background.
I'm reading Structure and Interpretation of Computer Programs right now and everything is clicking. I'm looking forward to going back to Clojure after I get through more of SICP.
Many people say that clojure is the one that clicked for them, but I suspect it's really not due to clojure itself, just that it's more recent and likely not to be the first lisp people have tried to learn.
I think the exposure of trying to learn different lisps is the key, and when you get to a point where one clicks, run with it!
Racket especially has a very accessible IDE, thorough documentation, and a few interesting books targeting different audiences. Plus, it's de facto a Scheme, so you can work through SICP with it.
I did Common Lisp back in college for several years, it's a great language. But good luck getting a job with it. Clojure on the other hand, I've been paying my bills with that for the last few years. The main difference is a much larger community, and therefore lots more libraries for real-world stuff, and sitting on the JVM you have a lot of options integrating with existing corporate stuff.
There's a Common Lisp implementation that sits on the JVM. It's called ABCL. It provides a Java API for loading and calling Lisp code as well as a Lisp API for calling Java code (although it is quite a bit more verbose than that provided in Clojure... nothing a few macros couldn't solve if it became a nuisance).
The community is what does it. I tried for years to get commercial work in Common Lisp. It never happened.
>"and Clojure is the first Lisp I'd consider using in production".
I can't really understand the logic in this. If anything, CL has more going for it for production usage, like ANSI standarization and many high-performance compilers.
Clojure has the advantage of targeting the JVM and Js runtimes. These are two most popular runtimes for writing web apps currently. Clojure can also be easily integrated into existing projects on these platforms.
Your Common Lisp code can also target the JVM runtime by compiling using the ABCL implementation (free, mature). It is ANSI compliant, so your code will work straight away with no change. ABCL also allows easy calling of Java libraries.
Furthermore, not only JVM: other CL compilers let you target the LLVM. And of course you also have native compilers like SBCL which produce blazingly fast code.
I really appreciate narcissism and arrogance of Rich - he spent these two years (or whatever the legend says) writing Clojure 1.0 before made it public and this is what arrogance is for.
Nevertheless, even an ordinary CS student would notice, that there is literally nothing extraordinary in Clojure which was not being researched and implemented in other languages, like Erlang, Scheme (that subset of it which does not include set-car! and set-cdr!) SML and, obviously, Common Lisp. The systematizing effort and ability to deliver are exceptional.
So, I am sorry, but I am not buying these marketing slogans, because I know on which principles it has been made.
> core.spec makes it more formal and verifiable. This is very useful when working with data, and is another example of where Clojure's pragmatism shines; the language is dynamic in its core, but when you need static typing - it's there, optionally
Um no. Spec is not static typing, it does runtime checks. And core.typed is currently being rewritten and is not compatible with current version of Clojure.
Nothing I've used has the combination of ease of use and interactivity. It's pretty difficult to explain, but one can send both top level forms, or smaller pieces of code to the repl and get results without leaving the code one is working on.
Clojure will affect the way you think about programming. It's led a number of people I know to start taking Haskell seriously after their Clojure phase.
This seems somewhat surprising to me since they are, in some sense, at opposite ends of the functional programming spectrum... but it's the path I took.
I think it's a cultural thing. There's something similar going on with Python and go.
Seems like a lot of people start with "getting types to go away makes a lot of powerful things easier". But eventually you a) get sick of typos causing runtime errors b) start to realise there are even more powerful things you can do with a non-broken type system.
I was fortunate enough to be given the opportunity to learn Clojure on the job. A majority of my work is on the web side (JavaScript, et al.), so learning Clojure was a fun and interesting challenge.
After the initial hurdle of getting used to a Lisp I found the language very liberating. I definitely approach problems very differently than I did prior to learning Clojure, even when flipping back to JavaScript land. I find my approach is vastly different that it was before. The best way to describe my way of thinking is that I'm very "data first" now.
Its basically modern perl. I've worked with both professionally, and the outstanding point with both has been:
- programmers try to be clever in their code, write one giant file full of complicated specialized 'beautiful' code, forget everything they know about breaking big tasks into simple small ones.
- code is a nightmare to maintain for non-author
- non-authors working on code results in adhoc mess of styles
Play with, sure. Learn a few things and watch the videos, absolutely. Rich is a really thoughtful talker. I actually quite enjoyed clojure before I had to collaborate on a code base.
...but goodness me. I cannot strongly enough recommend against using it professionally.
> - programmers try to be clever in their code, write one giant file full of complicated specialized 'beautiful' code, forget everything they know about breaking big tasks into simple small ones.
What on earth does this have to do with clojure? Programmers will do this in any language, there's nothing special about clojure here.
I personally don't find LISP languages to be the most readable, but I'm fairly sure it's subjective. If you learned LISP and then went to Java, your brain would probably struggle to process all the verbosity...
Rubbish. I've worked with a dozen languages, and its unquestionable that certain ones attact a high ratio of terse complicated code.
Clojure is specifically renouned (and celebrated) for its low LOC counts; people go as far as saying (literally on /r/clojure) given a choice of library, pick the one with a lower LOC.
That's not because people write simple pointless libs like lpad; its because the community actively encourages terse complicated code.
I've seen this logic about choosing a library but I think you are attributing a different rationale to it. I've seen it said if you find more than one library doing what you need, go through a hierarchy of criteria to choose and choose the first one that wins; in the event of a tie, evaluate the next point
1. pick the one with the most readable, understandable architecture
2. choose the one that focuses more on what you are looking for and not a slew of different things
3. choose the one with fewer lines of code
This third point is not "choose the more clever library" but more along the lines of choose the more straight forward and simple library. It's not code density you are evaluating but focus.
But honest question: Does where you work in the stack, meaning the level of abstraction, matter for language choice? Or vice versa?
I've written a few parsers in Java. Not terrific but not terrible either.
My (future) interest in Clojure is CSP. Mostly because I'm tired of wrestling with concurrency. But I'm not sure I'd want to be doing data processing with it.
> What on earth does this have to do with clojure? Programmers will do this in any language, there's nothing special about clojure here.
I haven't programmed in Java in over ten years, but one if its "advantages" (at least back in the day) was what I called its object-oriented handcuffs, which prevented programmers from being too fancy with their code. Every file had to be a class. Every class was an object. Namespaces were clearly and rigidly defined. Setters and getters were standard for defining properties of an object. Etc.
The net result was frustration for programmers who detested such rigidity, but relief for any new programmer coming into a project because Java made it very difficult to write the kind of "specialized beautiful code' that shadowmint laments.
Lisps in particular allow the kind of freedom that programmers love and project managers hate. And I say that as someone who loves writing code in lisp (racket in particular).
I've never seen anyone put more than one namespace in a single file in clojure. I don't think the compiler would even understand that...
While you can define classes in clojure, it's not the common way to do things, and so namespaces take the role of classes insofar as compartmentalizing code.
It's not normal to operate on data in another namespace directly... you can do it, but it doesn't feel natural in the language. You do it through an interface (called a protocol in clojure), or through functions in that namespace. So in that sense, you are still using getters and setters for everything.
But yeah, there is a lot of freedom in clojure. And that favors the writer, not the reader. Hence it can be difficult to grok somebody's code if they are more comfortable using the more advanced features of the language.
Java still has same issue too with bored engineers. Over engineering happens. AbstractSingletonProxyFactoryBean anyone? But really strongly typed languages like java is easier than most dynamic typing language to jump into _if the project is sane_.
Yeah, typing usually helps. Hiring and training programmers to follow good practices (like not doing what grandparent claims only happens in Clojure) helps even more :)
The J2EE culture (for lack of a better perjorative) negatively impacted Java's mindshare.
I still love me some Java for personal projects. But work Java usually means Spring, Hibernate, Maven, and other monstrosities. I'd hate Java too if that's all I knew.
Yeah, sorry, but having had to try and work with 10,000 line Java classes, I still disagree. (And don't even get me started on some of the abomination .jsp files I've encountered!)
Maybe there's more whitespace and boilerplate in Java, but it's just as possible for lazy programmers to not split their code into modules, or write horrible spaghetti code.
Object oriented languages also introduce an entire different class of potential problems too. Pointless interfaces and abstract classes, terrible sprawling inheritance hierarchies...
Oh, and I've also seen horrible walls of text written in PHP too! And JavaScript... and C#...
I've been working with Clojure professionally for the past 6 years, and this is completely contrary to my experience.
Clojure is the first language I've used where I'm able to easily read through libraries and understand them.
Clojure uses a small number of common patterns that are applied to solving a wide variety of problems. Most code in the wild that I've seen tends to actually look very similar, and follows common structure.
I've contributed to many Clojure libraries, and many people have contributed to libraries I maintain. My team also maintains Clojure projects that have been in production for years, and we find them much easier to maintain than comparable Java projects we've written previously.
Maybe you didn't have much experience in programming before learning clojure?
I never had any problems in understanding how a library worked in a language that I knew.
And I have looked at the source code of quite big libraries in my life.
I didn't say I couldn't understand them. I was talking about the amount of time it takes to understand a typical library in a language like Java. My experience is that you would have to spend a lot of time jumping through different classes, and often have to step through things in a debugger to understand them.
By contrast, I find that code in Clojure libraries to be much easier to read and understand. You literally needs orders of magnitude less code to solve same kinds of tasks, and it tends to be organized much better. I can open a namespace and read it top to bottom to see what it's doing. That's pretty much never the case with a non-trivial Java class.
As a concrete example consider the Java Flyway migrations library to the Clojure migratus library that I've taken over maintaining
They both provide same core functionality, and I was able to understand the Clojure version in a few hours. It would take me much longer to understand Flyway or to be meaningfully contribute to it.
I worked on a large-ish clojure project with ~15 (people left and joined) person team for years and I haven't experienced any of your points. Sounds more like an new project with a group of people not quite familiar with the language yet, which would have the problems you listed in any language.
Are we doing opinion threads now? Okay. I can do one too!
I do like working with clojure. It's my favorite dynamically typed programming language by an easy margin.
It's basically everything that was a good idea in Python and Ruby brought under the unifying role of a Lisp syntax, with a good Java FFI. This makes it slightly harder to learn (it's very difficult to learn Clojure if you don't at least know Java).
It has a very clean syntax for the most part. Parenthesis handling does need some automation (basically everyone copied paredit) but has the nice property that refactoring is a breeze and extremely predictable.
Except for rogue agents like me and a few others, very few programmers go crazy with defmacro, which makes Clojure often a lot more orderly and understandable than lots of Common Lisp code.
I've run a shop with 6 clojure developers collaborating with engineers in both mobile and web space, and in many places Clojure was such a delight that cljs began to creep into the web frontends. In many cases the mobile devs contributed API endpoint handlers to the codebase and they said in general it was easy to work with (although folks sometimes had problems with more complex map types; often because of leftover code from the Bad Old Days when :symbol-a and :symbol_a both got introduced into our codebase because of bugs in AWS bindings choking on -'s).
Clojure, like many dynamic languages, is ultimately limited by its lack of a type system. At some point systems become challenging to refactor. To combat this, a lot of discipline about testing and builds needs to happen.
I built a business around Clojure, sold it to a bank, and made lots of money and friends along the way (and just yesterday the bank announced they have decided to kill it because they don't like it, but that's got little to do with clojure and everything to do with <REDACTED>). We did stuff that VCs flat out claimed was impossible, to my face.
I'm much more interested in other languages now, but that's not because I think Clojure is bad. It's because I think that my development as a software engineer requires I put Clojure down for a few years and work with other concepts.
> it's very difficult to learn Clojure if you don't at least know Java
I'm curious why you think this is. As I've noted elsewhere in this thread, I've never written a line of Java in my life, and have worked as a Clojure dev since 2014. I also never write any code that interoperates with Java.
Seems to be a very common misconception that you must know Java to use Clojure.
> I'm curious why you think this is. As I've noted elsewhere in this thread, I've never written a line of Java in my life, and have worked as a Clojure dev since 2014. I also never write any code that interoperates with Java.
You're quite fortunate, but I was not. In fact, the reason I choose clojure at Level's primary language was for Java interop with the fintech world.
In many cases if you want performant code that doesn't leak resources, you will need to appeal to at the very least java.util.concurrent. Clojure doesn't even really try to fill the gaps there.
And even if you do, a working knowledge of the JVM, java compilation, and build/deploy tooling is quite helpful. There is only so much lein can abstract for you.
The very first thing I needed to do in Clojure was to deal with JWTs, which required using its Java interop. Decoding error messages sent me digging into the Java library's documentation.
It wasn't hard per se, and I didn't know Java, but I do see the disruption of my Clojure "flow" when I have to deal with Java interop as a big reason I'm not more of a proponent of the language.
I personally do not like the experience of interop in any language, or using FFIs. Perhaps that is why on any project I've had, I make sure to use high-level Clojure libraries that handle all the interop for me. So far, after nearly 4 years, that has not been a problem.
But then again, all my projects have been raw Clojure from the start, and never projects where I had to deal with an existing Java code base or handle internal Java APIs at a company, etc.
If you approach Clojure for a fresh project with the plan that it will just be a Clojure project, as I have, you will rarely if ever need to know a thing about Java.
This is slightly less true for ClojureScript where some knowledge of JS can come in handy if you want to use JS libraries (which nearly all front-end developers eventually require). But even there, most needs are already wrapped up in Clojure libraries.
>> ...the bank announced they have decided to kill it because they don't like it, but that's got little to do with Clojure...
I think that had something to do with Clojure. The thing is, businesses usually follow best-practices and choosing a language like Clojure is certainly not a best-practice (even if developers had really good reasons to use it).
For example, who maintains the code? How many Clojurists (is that a word?) can you find to work on the project? The bank would be better off by sticking to Java/Python/C#/etc.
Just because you can do it, doesn't mean that you should. That's a bad habit I've seen from many developers (honestly, I understand the interest to learn more languages and use them at work, but that often results in short-sightedness when building a project).
> How many Clojurists (is that a word?) can you find to work on the project?
You don't need to look for Clojurians, you just need to look for developers. Getting to write in some slightly off-the-standard languages is a big draw for a lot of competent devs
You know, I agree with this. But recently I was challenged by someone outside our industry that said, "Well why do you all give javascript's ecosystem such a hard time for cycling frameworks every 2 years or so?"
I don't have an answer. It feels different to me, though. And to me learning a new thing is just part of the job. But many engineers I talk to act like it's something you're forced into, not a thing you should embrace. Because of opportunity costs, etc.
Were you on the Level team at Capital One? If so, oh hai and I hope all is well. Given all the changes there, you may want to be careful. You gotta know this is against policy if it goes much further. I know because I got in trouble over violating said policy once... If not: no. You're wrong. Headcount issues were largely unrelated to Clojure. Also, please stop pretending your best guess is some sort of calculus over complex corporate politics.
Staffing as a small startup bought by a big company is always a problem. Doubly so when you're on the leading edge of a corporate introduction into a new (and in many ways more fiercely competitive) labor market.
> can you find to work on the project? The bank would be better off by sticking to Java/Python/C#/etc.
I had no problem recrutin good clojure developers once I worked with the recruting team to help get the job postings in the right places.
> Just because you can do it, doesn't mean that you should.
The converse of this that is equally true: Just because you don't feel capable of doing it doesn't mean I should not. This is no idle speculation, we did it. We exited. We exited with a featherlight team, which turned a small and interesting project into a major opportunity for every employee.
> I think you should try to be more civil on HN. You can't just bash someone because of their guess.
I asked politely, my friend. I'm suggesting your guess, leveled as an authoritative position, is misleading. Its you, taking my story away from me, asserting your narrative based strictly on preference. It's a narrative I disagree with, so I'm not inclined to let it slide.
It's crass to do this. There are many, MANY other ways you could have decided to open this subject. You could have related to YOUR experience, you could have asked for explanation of how these problems are perceived or surmounted. You could have simply expressed skepticism about the team size.
But you didn't. Here we are, on the wrong foot.
> I don't want to get into some argument with someone who feels he is the only guy capable of achieving a failure.
Then worry not! I am not: a guy, the only one capable of achieving, nor do I consider the product that made me a millionaire a failure.
I've also worked with both Perl and Clojure professionally. I think Clojure code, by nature, has a higher chance at being "beautiful" or elegant, or at the very least as a close proxy, minimal. I feel that doing a lot with a little is easier in Clojure, and it makes these "beautiful" code snippets less likely to be mysterious and more likely to be understood by maintainers down the line, even sans documentation.
The non-author comment is well taken though, as I find Clojure development to have a much higher thinking-to-coding ratio, so when a solution gets translated from the author's head to code, much of the complexity can be hidden due to the conciseness and lack of verbosity. I didn't really find that to be a problem in Perl, however, with the exception of perhaps the (over/ab)use of complicated regular expressions.
Remove regex abuse and newb/non-programmer code structure and Perl as written by a knowledgeable person is a most beautiful language. When yourself are knowledgeable and understand the "magic" it is beautiful beyond most languages.
Sorry, but don't agree with what you are saying. Clojure is very different from Perl. I would argue Clojure is more readable than Java (when written well). Immutability by default means Clojure code can be read like math equation on a board. Sure, it's tough if you don't put in the effort to learn the notation, but once that barrier is crossed, it's a very usable language.
Also, this notion of code should be readable by everyone (not involved with the project) is bullshit. Learn the notation once, make use of it forever. All that is needed to do to find the grain of the problem, and cut along it (and REPL helps a lot with that). Contrarily, you can write code assuming no background - which leads to Java like code, where everything must be repeated.
Perl is a write-only context-dependent (with side-effects too) language, because Larry Wall thinks computer languages should be like human languages.
Clojure also affected the way I think about programming in a very positive way. There are downsides to it
- stack traces
- jvm
- lein is pretty slow
but the upsides
- immutable data structures
- lisp/macros
- repl (you can send code from your editor to the repl)
- community
- great open source web app libraries (for me, anyway)
outweigh the downsides.
I encourage everyone here to try clojure at some point, it might just become your favorite language.
For people familiar with java, the jvm is an upside. Being able to use libraries (especially client ones) from your project can be very useful. The amazing interop support is very nice if your pragmatic over purist and don't plan on rewriting a ton of things.
This is true, if you're a java/jvm person, this is a huge upside, even if you aren't, once you see just how much code has been written in java, you'll probably come to appreciate the pragmatism.
When I learned clojure two years ago, I didn't have any experience with java or the jvm, so I had to sort of learn where the boundary was.
After that initial hurdle however, I now use java interop quite a bit.
By far the most incredible thing for me was to see how far a few characters of code can go. Clojure is a very tight language where you can accomplish a lot in just one line of code. You end up suddenly having all this new, extra time to just think about your problem instead of coding it.
Another interesting side-effect of tight code, is that you become a lot less attached to it. When I worked in Java, it would often take 100s of lines of code to solve a simple problem, and once I wrote that code I'd be very hesitant to throw away or refactor it.
With Clojure, it often takes 10 lines or less to solve problems that take 100s of lines of Java. This encourages a lot more experimentation and refactoring in my experience.
Also, since you're always working in the REPL, you can always run the code after you change it and see that it's doing what you want immediately.
I was lucky enough to land a Clojure job (stitchdata.com, we're hiring!) before having to learn Clojure, because I had the benefit of experienced mentors helping me get to grips with lisps (which were alien to me) and a development workflow that leaned heavily on the REPL (emacs + cider). Clojure has changed the way I think about many programming concepts and has been a joy to learn. It has it's warts but the core is very well built and I find the documentation to be quite good once you understand the basics.
That's true of any language. Even within the same language paradigm.
If you know C#/Java then learn C++ or vice versa and it'll affect the way you think about programming.
Or move on to different paradigms...
Go outside of the statically typed OO world and try out the dynamically typed OO languages like ruby or python.
Even better, learn a weakly typed procedural language like C ( lingua franca for CS ).
Or choose an architecture and learn some assembly. Now that will affect the way you think about programming.
Move out of onto a different domain altogether and try one of the flavors of a declarative language like SQL.
Learning a bit here and there of many different languages and paradigms will affect the way you think. Clojure isn't special.
You can even just stay within a framework and try to get a deep understanding of .Net language/IL/Framework Libraries/Runtime stack. Then you get into the debate of breadth vs depth. Should you learn one language/framework/paradigm exceptionally well or should you learn a bit about a lot of a lot languages/paradigms?
225 comments
[ 3.4 ms ] story [ 257 ms ] threadMany Clojure libraries cross-compile between Clojure and ClojureScript. You can see an example here https://github.com/yogthos/json-html/blob/master/src/json_ht...
Erlang is another widely used functional language, but it's not very widely used outside the telecom niche. Clojure has the advantage of running on the JVM, and makes it easy to target a much wider range of domains.
Scala's problem is that they marketed it as "we're almost java - you can just sprinkle some functionalness on your code as you go and you'll be fine".
Furthermore, if you wanted to, you could reimplement the internals in Clojure and the users of the library wouldn't be affected.
Being able to leverage existing mature libraries is a huge advantage, and the fact that they can be used via idiomatic functional APIs provides the best of both worlds experience.
Wrapper libraries are one way to deal with hybrid code.
Perhaps, though unlikely[1] (Haskell seems to have been picking of steam of late).
[1] https://trends.google.com/trends/explore?date=today%205-y&q=...
Clojure is still restricted/limited compared to Common Lisp.
- Proper stack traces. Debugging Common Lisp code is not only easy, it is far easier than in 90% of programming platforms out there. Clojure is severly lacking here; it has been promised that there will be an improvement; i will be watching since this will greatly improve Clojure
- "Lisp-2 versus Lisp-1": On Common Lisp, functions and variables have separate namespaces so they don't conflict. This makes writing code, especially macros much easier. Macros are one of the biggest reasons to use a Lisp dialect in the first place, so this is a rather strong point.
- Object orientation facilities: CLOS, CL Object System, is arguably the most powerful OOP system on a mainstream language. You should really take a look at it, it can just blow your mind. Clojure has no comparable OOP implementation. I recommend CLOS to anyone that feels disillusioned with OOP.
I could stop here, because the three points above are very strong differences, but there are some other points to consider:
- Standarization and portability (1/2): CL is an ANSI standard, and there are many ANSI-compliant, mature, compilers: LispWorks, SBCL, Allegro CL, ABCL, Clozure CL, ECL, CLISP, and others. There are features that the ANSI standard does not cover, like interacing with C libraries, but there are already portable libraries that allow you to also do this on a portable way. So, in this way, your code can be compiled by those implementations, often without any change needed at all.
- Standarization and portability (2/2): The ABCL compiler allows you to target the JVM and call Java code from Lisp or call Lisp code from Java. The Clozure compiler lets you target the LLVM. Practically most modern platforms can be compiled from CL to native code by using the suitable compiler.
- Interfacing: As mentioned above, you can interface with Java, and with C, as needed. C interfacing is very easy, btw.
- Speed: CL can be very fast. For example SBCL is really fast, particularly with numbers where it can be as fast as C. In general it is in the same speed of Java code running under the Oracle's latest JVM. I think that CL code under SBCL probably should be much faster than Clojure running under JVM.
- Number data types: CL supports, with high perfomance and natively: Complex numbers, Rational (fractional) numbers, floating numbers (with the IEEE standard), integers, binary numbers, and arbitrary-length numbers. Bonus: The same typical operators (+,-,*,/) work perfectly with them, so using them is simple. In this feature, CL fares even better than Fortran (seriously.)
- Static typing? CL is not statically typed, but the standard allows you to declare the type of your variables. A compiler like SBCL will catch many type errors during compilation (and of course they will also be catched at runtime). Doing this has also the big bonus of increasing performance dramatically.
- Mainstream: While not popular, Common Lisp is a mainstream language, so there are tons of books and resources out there, as well as having been used successfully complex production systems, like for example auto-piloting the Deep Space 1 spaceship (NASA) for days.
I agree with Wadler that the best programming languages are discovered. First, the principles are revealed in a proof by a logician. Then about two or three decades later computer scientists "discover" the same principles. And another two or three decades after that mainstream programming languages reluctantly integrate the ideas.
This is all to say that computer languages are behind the curve. If your goal is to expand your brain you're better off digging into the fundamentals behind computer languages. Then you can see the flaws and trade-offs in all languages including Clojure, Haskell, C, Agda, Idris, Javascript, etc, etc, etc.
I've said the Clojure is a decent language. I don't think there is a perfect one. Not yet.
That's just a non-user's perspective on the scuttlebutt, though - I don't have any actual experience with Go.
On the other hand, Clojure is more production-ready than most Schemes, in my experience -- this sentence being one of the reasons as the Scheme landscape is very fragmented, despite the standard(s). For example, in my own SICP journey (http://eli.thegreenplace.net/tag/sicp) I was using PLT Scheme, one of the most usable implementation back in the day. Unfortunately it forked off into a slightly different language - Racket - since then. So YMMV
Scheme is simpler though, and probably more approachable for a beginner. The hosted nature of Clojure means you'll need a decent understanding of the underlying JVM to understand errors--the first time you get a stack trace you're going to seriously question your choice. Also Clojure's syntax is more complicated (but really, not at all complicated compared to Java, C#, Python, etc.).
You should be able to pick up Clojure pretty quickly if you're already familiar with Scheme, the programming style is pretty similar.
If this would be common practise nobody would learn golang.
FWIW, I think most language can affect thinking - but the degree varies.
For a huge number of developers coming from the Javascript/web side of things, Clojurescript is poised so perfectly to usher them into thinking about composing your programs as pure functions, immutable data structures and organizing your state such that it is easy to reason about.
Eventually, Clojure led me to Haskell, and even back to how Javascript ought to be written (i.e. fantasy-land specs). This has been the single most eye opening learning effort that I have ever had the joy of undertaking since I started programming.
Just put enough effort to get beyond that "omg parenthesis" barrier and it will be a delight. It's like that Half-life joke: there are two kinds of people, those that finished Half-life many times and those that never got off the train (which as somebody will probably point out, is a variation on another joke, but you get the idea).
totally unaffiliated: https://www.braveclojure.com/clojure-for-the-brave-and-true/
The other immediate barrier is when Clojure barfs an ugly stack trace. I've heard that 1.9 will address that with core.spec, but I haven't explored it yet.
About the parens, it's true it's one great thing, every construct has a value [1].
It's funny because You hear about object orientation where things are uncoupled and standalone yet the language isn't this way. Lisp has this, it's the incarnation of its own idea. But it looks silly if you're in it for the syntactic appeal.
[1] Although I often wish for a helper thing when evaluating the body of, say a let to grab the bindings instead of saying "unknown var a in (inc a)". This is lisp in general
That said, the info it dumps is in the form of clojure data structures. In my own programs, I've added code to refine this output to something useable, but it's particular to my project and I don't think generally useful.
There are other projects to aid in this fashion too. Expound is one that takes inspiration from Elm, and Inspectable is intended to help at the repl by launching a gui for exploring Specs and failures.
I've tried both, but couldn't get inspectable to work in my environment, and expound isn't meant to handle the error I've been dealing with, so I can't speak to either.
If you have some time, would you be willing to expand on this? I'm curious to learn more about your error. I'm the author of Expound so I'd like to make it more useful (or, at least, understand the limits of a library like Expound).
Clojure default of immutable hash maps and vectors was really bold. They had to innovate by giving a twist to Bagwell's great research on data-structures, and they managed to build what in my opinion are the first set of general data-structures suitable of representing traditional hierarchical object-tree data-models immutably, at the scale of large interactive software.
Without these, the single-atom architecture that now is popular as ever with Redux, Elm, etc. would not be possible. The flourishing of functional programming for interactive software that followed shows to me that the biggest impediment before was not the lack of functional ways of expressing interactions (the Haskell people have devoted a lot of notable effort in the FRP camp). Instead, just having efficient data-structures to write persistent document models, escaping the rigidity of lists and rb-trees, allows you to write big interactive sofware in a functional manner even with the most dumb abstractions (which is a good thing, KISS!) and even stupid language (JavaScript!).
This has inspired me to work on a generic implementation in C++, which also has experimental Python and Guile bindings: https://sinusoid.es/immer/ https://github.com/arximboldi/immer/
In the past I also did some other work on bringing Clojure Transducers to C++ (https://www.youtube.com/watch?v=vohGJjGxtJQ&t=1614s) but I know consider it a much less important effort---without the data-structures the other parts of Clojure feel almost like just sugar.
EDIT1 -- Sorry for the shameless plug ;)
EDIT2 -- Grammar
I don't know enough about haskell to say what it does or doesn't have in relation to clojure. However, it's not simply the data structures, but how the language is built to interact with them from a programmers perspective.
From a user interface to the language point of view, I find the ways of interacting with maps to be very nice.
In particular the dual purpose of "keywords" to serve both as a good index/key in said hash maps, but also as callable look up function for the same key in a hash map leads to simplified code.
This, coupled with the extremely high usage of those maps in typical clojure code, makes a big difference.
To expand on that a little, "keywords" are symbols that begin with a colon, which I believe is the same as common lisp. :for :example :this :is :a :sentence :of :keywords. They are commonly used as the key in a map. Which has a literal syntax like (def map1 {:a 1 :b 2 :c "taco"}). To pull a value out, you can evaluate (:c map1) which will return "taco".
They are hash maps based on work by Philip Bagwell, and I think a lot of languages have versions of this now. At least scheme and Common Lisp do. But I don't believe they have the nice literal syntax and interaction syntax.
Many Lisp traditionalists are bothered by the use of curly braces for maps and square brackets for vectors, however I'm a fan. Without this bit of syntax, you have to add verbosity to the language to differentiate defining a map from doing a lookup, etc.
For resources, Clojure for the Brave and True is pretty popular. Also the online community is quite friendly, although I have noticed some trends toward "the one true way" of doing things that puts me off a little.
I have definitely seen this. I'm not sure that I understand this sentiment. The only thing that Clojure is missing is the "system" feel that Common Lisp provides, with things like character macros, symbol macros, etc.
That being said, I definitely appreciate how thought through Clojure is, especially when compared to CL. Several things are missing/don't feel well thought out in CL (notion of a `calleable` type is gone, `nth` isn't defined on vectors or strings (but you can still `length` them!) nor is `first` and `rest`) That provides a great deal of friction.
You can do this using the metaobject protocol and defining a class with a metaclass of FUNCALLABLE-STANDARD-CLASS, for example, given:
Then (funcall (make-instance 'c :x 3)) will print "I'm #3".> `nth` isn't defined on vectors or strings (but you can still `length` them!)
ELT in Common Lisp is generic over sequence types. NTH is specific to lists. If you want to write a function that is generic over sequences, use ELT. If you know that you have a list or an array, and you're writing something performance sensitive, you can avoid type dispatch overhead by using NTH or AREF instead of ELT.
> nor is `first` and `rest`
You can avoid that friction a lot of the time by writing in terms of MAP, REDUCE, REMOVE-IF-NOT, and so on, as well as LENGTH and ELT, which are all generic over sequences, or doing (iter (for x in-sequence sequence) ...) or whatever.
But if you really want to use FIRST and REST to walk through a vector, you can do (coerce vector 'list) first to convert it. Coercing a list to list will just return that list, so that version would work for either, too. I admit, there's no real reason I know of (aside from lack of demand) that there isn't a predefined function like:
Then if it bothered you you could do something likeClojure OTOH has a relatively straight-forward compiler written in Java, only targetting the JVM in a standard way. The Java/JVM infrastructure for code generation (JIT), low-level optimizations (JIT), memory management, application building and delivery could be reused. With all its features and limitations (for example no TCO, fixed JVM instruction set, ...).
Common Lisp also not only provides some generic operators, but also lots of non-generic operators. One reason for that is the will for backwards compatibility (which was another goal in the Common Lisp design) and the idea that something like Common Lisp is also a low-level languages or provides at least a layer of low-level stuff, functions you would use to implement higher-level stuff.
Common Lisp is the next step in the evolution of Lisp I to Lisp 1.5 to Maclisp to Zetalisp. Thus it contains much of the core Lisp functions, stuff you can read about it the Lisp manuals from 1958 and 1960. People did not want to throw their code away, they wanted a modernized Maclisp. Thus one could port a 100kloc Lisp program from Maclisp or Zetalisp to Common Lisp without too much effort. The main operations were there.
Clojure OTOH has almost no backwards compatibility to earlier Lisp dialects. Programs can not be ported from Lisp to Clojure in a useful way. They have to be rewritten. But that was okay, since the main target were not Lisp developers, but people who wanted a language with Lisp influence in the Java/JVM eco-system (or possibly other eco-systems too).
Thus Common Lisp looks to you like it has less thought put in, where people actually struggled to be backwards compatible and modernize the language at the same time.
So Common Lisp has lots of non-generic functions, a layer of generic non-extensible functions (like the sequence layer) and a mechanism for extensible generic functions (CLOS). Something like CLOS is implemented on-top of the non-generic Lisp, with a few parts using implementation specific mechanims. One does not need to use the primitives of a different host language, because Common Lisp is already capable of hosting with its primitives.
I agree with you, in the sense that they are useful, however the complain of "Lisp traditionalists" will be that in any case, if you need them, you can add them to Common Lisp by using reader macros. So in CL the curly braces and brackets are "free" to be used (i.e. define what they should do) in any way you want; on Clojure they are already taken, so it feels a bit uncomfortable.
Not (map1 :c)? A bit odd.
They are pretty amazing. Of course there are implementations in Haskell now, but the reason they are especially cool in Clojure is how well they integrate with the language and its other features.
[1] https://stackoverflow.com/questions/8844707/how-is-a-bitmapp...
You might also Okasaki's Purely Functional Data Structures, which ends up in the citations for a lot of these.
[0] https://bendyworks.com/blog/leveling-clojures-hash-maps
If you allow me another shameless plug, I also compiled a good bunch of references in the "Related Work" section of my own paper on immutable vectors: https://public.sinusoid.es/misc/immer/immer-icfp17.pdf
Very few clojure programmers consider Transducers very important even in Clojure. While they're convenient for implementing Clojure's stdlib and they have some nice properties, they're very difficult to work with. It takes a very experienced clojure programmer to use transducers. Heck, I know some famous clojure developers who admit they themselves don't feel confident using them.
But the main challenge for them is that transducers just scream for some basic type validation so that you don't accidentally chain together nonsense. C++ could provide that, and you'd end up with all the same properties. In particular, they offer something like half the monad laws and a more easily composed form of CPS (how your continuation is called is more predictable).
I'd think that might be a really valuable thing in C++, as it's a construction that reproduces stream fusion.
Yet to dare clojure spec but it feels likely that the next mind blowing journey will be therein.
I don't use so many `->>` anymore thanks to them.
OR, I'd rather deal with a direct actor model a la Erlang, Pony and Cloud Haskell.
Go's experience is so goddamn miserable it should just put the entire notion of async channels as a higher level programming library to bed. As a low-level primitive, it's okay but not fundamental.
Actually one of the things I found during my work on transducers is that actually they seem to me more implementable in C++ than in Haskell. For example, you can implement state-less transducers with almost zero overhead, something that in Clojure is definitely not true (so the standard transducers do hide state inside the closure of the reducing functions). It is also easier to do n-ary transducers for zipping and unzipping and everything, as you intuit, is type checked.
Still, I agree that transducers are most useful when you have libraries that make sense of them. For example, you could implement a lot of "Rx" using transducers. Ideally I see a user never writing a transducer, and occasionally passing one to some library that provides "higher order" reactive collections (channels, observables, etc.) that can be transformed with transducers. For example, my motivation to implement them in C++ was to build "observable/reactive lenses" (something like "cursors" in Om) [1] where you could use transducers to specify how you "zoom" into the virtual sequence of state values...
Anyways, they are an interesting coding tool, but they don't really influence the architecture of the application.
[1] atria::funken https://github.com/Ableton/atria
If you don't mind the overhead, it's ContT wrapping some base monad. Or you could make it integral. That is more specific than transducers, I grant. But there's not much they can do that ContT over like Array does not.
I think it's too bad they were invented in Clojure, because they're brutally hard to use right and write in clojure.
I have some interesting work I could share in private on using them to provide stream fusion properties to pipelines of channels. Hit me up on keybase if you'd like to see. I don't want to open source it because then people will expect me to explain them and maintain them and I don't feel like it :)
How does it hold up for those of us that really love static typing and compilers?
as for compiler part - you're in for a treat. clojure is a compiled language - everything compiles down to jvm bytecode. the surprise part is that due to homoiconicity you get a compiler that's happy to let you hook into particular pieces of your code and (since code is literally represented as usual clojure data structures - lists/maps/vectors/etc) modify it on the fly before compiling. you basically define `f(code) -> other_code` - the ultimate metaprogramming.
Most of the reasons the author presents would either be expanded in one of those languages, or is immaterial to the goal of maximizing learning. After reading the article I have no more reason to consider learning Clojure over learning any of the languages above.
Also, anybody who wants to try FP style programming, but isn't interested in static typing is much better off with Clojure. Type systems in languages like Haskell and Idris add a lot of complexity and mental overhead that's not present in a dynamic language.
- You can evaluate any part of editor in console with keyboard shortcut. The only difference between using console and editor is that your input is easily saved in editor.
- The environment browser make inspecting variables, data structures much easier.
Besides, in R you want to use vectorized functions for better performance, so you search for general functions and combine them, which actually promote good functional programming style instead of a big control block with many processes intertwined.
I use CIDER with Emacs for Clojure integration: https://github.com/clojure-emacs/cider
As for type systems increasing complexity I think that depends on the application. As a beginner to Clojure I would pass unstructured data around all over the place and then have the mental overhead of trying to remember the structure or fix it all at runtime.
I did a talk last year where I illustrate it starting around 15 minute mark https://www.youtube.com/watch?v=nItR5rwP4mY
I'm not aware of any non-Lisp languages that provide anything close to that.
The REPL driven workflow directly addresses the problem you're describing as well. When I'm working with Clojure, I never write a lot of code before running it. Each time I write a function, I run it and see exactly what it's doing. This means that I never have to keep a lot of context in my head.
Let's say I need to pull some data from the database, massage it, and return it to the client. I would write the function to read the data, run it to see what shape of the data I get. Then, I would write the function that consumes that data and transforms it. Again, I'd run it and see that I have the data I want, and so on. At each step of the process I only need to consider the last step and the next.
Also, Spec and Schema are commonly used in libraries to provide the description of the data at the API level. This is where I really care what the shape of the data is, as opposed to typing every single function I write.
What surprises me is that none of mainstream languages provide this kind of environment. Lisp and Smalltalk have been around for many decades, and somehow this workflow ended up being completely neglected in the mainstream.
Programs aren't usually directly portable between languages primarily for that reason - the language designers optimise for a specific way they want their language to be used, then everyone else follows, and eventually you're going against an entire ecosystem if you want to do something different.
I felt like an idiot while I was learning Haskell; but in Clojure I only realised my idiocy after perhaps a year of working in it. Clojure feels deceptively close to more standard languages, but in its own way is as different to them as Haskell. It just isn't as immediately obvious.
Clojure will expand it in different ways. Here's some examples:
I think Clojure is a great language, but if you just want to expand your mind via a Lisp then I think you'll get more for your time with another implementation.
What's Racket's killer library that's doing something no one else is?
I don't know about "killer" but Racket's continuation based web server was / is novel (maybe first?). https://docs.racket-lang.org/web-server/
Also, it's not a library, but the whole #lang thing seems to be the coolest game in town for implementing DSLs and other more fully featured languages.
I'm reading Structure and Interpretation of Computer Programs right now and everything is clicking. I'm looking forward to going back to Clojure after I get through more of SICP.
I think the exposure of trying to learn different lisps is the key, and when you get to a point where one clicks, run with it!
Scheme:
The Little Schemer (a classic)
Common Lisp:
Land of Lisp (a truly remarkable, quaint book: learn Common Lisp by programming classic games; full with really funny cartoons.)
Practical Common Lisp (really excellent way to learn quickly, modern. Freely available.)
The community is what does it. I tried for years to get commercial work in Common Lisp. It never happened.
>"and Clojure is the first Lisp I'd consider using in production".
I can't really understand the logic in this. If anything, CL has more going for it for production usage, like ANSI standarization and many high-performance compilers.
Furthermore, not only JVM: other CL compilers let you target the LLVM. And of course you also have native compilers like SBCL which produce blazingly fast code.
Here's an excerpt:
Why did I write yet another programming language? Basically because I wanted:
A Lisp for Functional Programming symbiotic with an established Platform designed for Concurrency and couldn’t find one.
Nevertheless, even an ordinary CS student would notice, that there is literally nothing extraordinary in Clojure which was not being researched and implemented in other languages, like Erlang, Scheme (that subset of it which does not include set-car! and set-cdr!) SML and, obviously, Common Lisp. The systematizing effort and ability to deliver are exceptional.
So, I am sorry, but I am not buying these marketing slogans, because I know on which principles it has been made.
The particular combination/integration of features and the embedding in a Java environment is what made it liked by its target audience.
Um no. Spec is not static typing, it does runtime checks. And core.typed is currently being rewritten and is not compatible with current version of Clojure.
https://s3.us-east-2.amazonaws.com/photoblobs/screencast_201...
Seems like a lot of people start with "getting types to go away makes a lot of powerful things easier". But eventually you a) get sick of typos causing runtime errors b) start to realise there are even more powerful things you can do with a non-broken type system.
After the initial hurdle of getting used to a Lisp I found the language very liberating. I definitely approach problems very differently than I did prior to learning Clojure, even when flipping back to JavaScript land. I find my approach is vastly different that it was before. The best way to describe my way of thinking is that I'm very "data first" now.
Its basically modern perl. I've worked with both professionally, and the outstanding point with both has been:
- programmers try to be clever in their code, write one giant file full of complicated specialized 'beautiful' code, forget everything they know about breaking big tasks into simple small ones.
- code is a nightmare to maintain for non-author
- non-authors working on code results in adhoc mess of styles
Play with, sure. Learn a few things and watch the videos, absolutely. Rich is a really thoughtful talker. I actually quite enjoyed clojure before I had to collaborate on a code base.
...but goodness me. I cannot strongly enough recommend against using it professionally.
What on earth does this have to do with clojure? Programmers will do this in any language, there's nothing special about clojure here.
I personally don't find LISP languages to be the most readable, but I'm fairly sure it's subjective. If you learned LISP and then went to Java, your brain would probably struggle to process all the verbosity...
Clojure is specifically renouned (and celebrated) for its low LOC counts; people go as far as saying (literally on /r/clojure) given a choice of library, pick the one with a lower LOC.
That's not because people write simple pointless libs like lpad; its because the community actively encourages terse complicated code.
1. pick the one with the most readable, understandable architecture 2. choose the one that focuses more on what you are looking for and not a slew of different things 3. choose the one with fewer lines of code
This third point is not "choose the more clever library" but more along the lines of choose the more straight forward and simple library. It's not code density you are evaluating but focus.
But honest question: Does where you work in the stack, meaning the level of abstraction, matter for language choice? Or vice versa?
I've written a few parsers in Java. Not terrific but not terrible either.
My (future) interest in Clojure is CSP. Mostly because I'm tired of wrestling with concurrency. But I'm not sure I'd want to be doing data processing with it.
I haven't programmed in Java in over ten years, but one if its "advantages" (at least back in the day) was what I called its object-oriented handcuffs, which prevented programmers from being too fancy with their code. Every file had to be a class. Every class was an object. Namespaces were clearly and rigidly defined. Setters and getters were standard for defining properties of an object. Etc.
The net result was frustration for programmers who detested such rigidity, but relief for any new programmer coming into a project because Java made it very difficult to write the kind of "specialized beautiful code' that shadowmint laments.
Lisps in particular allow the kind of freedom that programmers love and project managers hate. And I say that as someone who loves writing code in lisp (racket in particular).
While you can define classes in clojure, it's not the common way to do things, and so namespaces take the role of classes insofar as compartmentalizing code.
It's not normal to operate on data in another namespace directly... you can do it, but it doesn't feel natural in the language. You do it through an interface (called a protocol in clojure), or through functions in that namespace. So in that sense, you are still using getters and setters for everything.
But yeah, there is a lot of freedom in clojure. And that favors the writer, not the reader. Hence it can be difficult to grok somebody's code if they are more comfortable using the more advanced features of the language.
Indeed! In fact, this is one of the sanest ways to do something resembling OO in Clojure.
I still love me some Java for personal projects. But work Java usually means Spring, Hibernate, Maven, and other monstrosities. I'd hate Java too if that's all I knew.
Maybe there's more whitespace and boilerplate in Java, but it's just as possible for lazy programmers to not split their code into modules, or write horrible spaghetti code.
Object oriented languages also introduce an entire different class of potential problems too. Pointless interfaces and abstract classes, terrible sprawling inheritance hierarchies...
Oh, and I've also seen horrible walls of text written in PHP too! And JavaScript... and C#...
Clojure is the first language I've used where I'm able to easily read through libraries and understand them.
Clojure uses a small number of common patterns that are applied to solving a wide variety of problems. Most code in the wild that I've seen tends to actually look very similar, and follows common structure.
I've contributed to many Clojure libraries, and many people have contributed to libraries I maintain. My team also maintains Clojure projects that have been in production for years, and we find them much easier to maintain than comparable Java projects we've written previously.
By contrast, I find that code in Clojure libraries to be much easier to read and understand. You literally needs orders of magnitude less code to solve same kinds of tasks, and it tends to be organized much better. I can open a namespace and read it top to bottom to see what it's doing. That's pretty much never the case with a non-trivial Java class.
As a concrete example consider the Java Flyway migrations library to the Clojure migratus library that I've taken over maintaining
https://github.com/flyway/flyway/tree/master/flyway-core/src...
https://github.com/yogthos/migratus/tree/master/src/migratus
They both provide same core functionality, and I was able to understand the Clojure version in a few hours. It would take me much longer to understand Flyway or to be meaningfully contribute to it.
I do like working with clojure. It's my favorite dynamically typed programming language by an easy margin.
It's basically everything that was a good idea in Python and Ruby brought under the unifying role of a Lisp syntax, with a good Java FFI. This makes it slightly harder to learn (it's very difficult to learn Clojure if you don't at least know Java).
It has a very clean syntax for the most part. Parenthesis handling does need some automation (basically everyone copied paredit) but has the nice property that refactoring is a breeze and extremely predictable.
Except for rogue agents like me and a few others, very few programmers go crazy with defmacro, which makes Clojure often a lot more orderly and understandable than lots of Common Lisp code.
I've run a shop with 6 clojure developers collaborating with engineers in both mobile and web space, and in many places Clojure was such a delight that cljs began to creep into the web frontends. In many cases the mobile devs contributed API endpoint handlers to the codebase and they said in general it was easy to work with (although folks sometimes had problems with more complex map types; often because of leftover code from the Bad Old Days when :symbol-a and :symbol_a both got introduced into our codebase because of bugs in AWS bindings choking on -'s).
Clojure, like many dynamic languages, is ultimately limited by its lack of a type system. At some point systems become challenging to refactor. To combat this, a lot of discipline about testing and builds needs to happen.
I built a business around Clojure, sold it to a bank, and made lots of money and friends along the way (and just yesterday the bank announced they have decided to kill it because they don't like it, but that's got little to do with clojure and everything to do with <REDACTED>). We did stuff that VCs flat out claimed was impossible, to my face.
I'm much more interested in other languages now, but that's not because I think Clojure is bad. It's because I think that my development as a software engineer requires I put Clojure down for a few years and work with other concepts.
I'm curious why you think this is. As I've noted elsewhere in this thread, I've never written a line of Java in my life, and have worked as a Clojure dev since 2014. I also never write any code that interoperates with Java.
Seems to be a very common misconception that you must know Java to use Clojure.
You're quite fortunate, but I was not. In fact, the reason I choose clojure at Level's primary language was for Java interop with the fintech world.
In many cases if you want performant code that doesn't leak resources, you will need to appeal to at the very least java.util.concurrent. Clojure doesn't even really try to fill the gaps there.
And even if you do, a working knowledge of the JVM, java compilation, and build/deploy tooling is quite helpful. There is only so much lein can abstract for you.
It wasn't hard per se, and I didn't know Java, but I do see the disruption of my Clojure "flow" when I have to deal with Java interop as a big reason I'm not more of a proponent of the language.
But then again, all my projects have been raw Clojure from the start, and never projects where I had to deal with an existing Java code base or handle internal Java APIs at a company, etc.
If you approach Clojure for a fresh project with the plan that it will just be a Clojure project, as I have, you will rarely if ever need to know a thing about Java.
This is slightly less true for ClojureScript where some knowledge of JS can come in handy if you want to use JS libraries (which nearly all front-end developers eventually require). But even there, most needs are already wrapped up in Clojure libraries.
I think that had something to do with Clojure. The thing is, businesses usually follow best-practices and choosing a language like Clojure is certainly not a best-practice (even if developers had really good reasons to use it).
For example, who maintains the code? How many Clojurists (is that a word?) can you find to work on the project? The bank would be better off by sticking to Java/Python/C#/etc.
Just because you can do it, doesn't mean that you should. That's a bad habit I've seen from many developers (honestly, I understand the interest to learn more languages and use them at work, but that often results in short-sightedness when building a project).
You don't need to look for Clojurians, you just need to look for developers. Getting to write in some slightly off-the-standard languages is a big draw for a lot of competent devs
One of the best comments in HN in a long time, deserves its own article.
So true. A programmer should be able to learn any other new language quickly.
I don't have an answer. It feels different to me, though. And to me learning a new thing is just part of the job. But many engineers I talk to act like it's something you're forced into, not a thing you should embrace. Because of opportunity costs, etc.
Were you on the Level team at Capital One? If so, oh hai and I hope all is well. Given all the changes there, you may want to be careful. You gotta know this is against policy if it goes much further. I know because I got in trouble over violating said policy once... If not: no. You're wrong. Headcount issues were largely unrelated to Clojure. Also, please stop pretending your best guess is some sort of calculus over complex corporate politics.
Staffing as a small startup bought by a big company is always a problem. Doubly so when you're on the leading edge of a corporate introduction into a new (and in many ways more fiercely competitive) labor market.
> can you find to work on the project? The bank would be better off by sticking to Java/Python/C#/etc.
I had no problem recrutin good clojure developers once I worked with the recruting team to help get the job postings in the right places.
> Just because you can do it, doesn't mean that you should.
The converse of this that is equally true: Just because you don't feel capable of doing it doesn't mean I should not. This is no idle speculation, we did it. We exited. We exited with a featherlight team, which turned a small and interesting project into a major opportunity for every employee.
I think you should try to be more civil on HN. You can't just bash someone because of their guess.
> The converse of this that is equally true: Just because you don't feel capable of doing it doesn't mean I should not.
I don't want to get into some argument with someone who feels he is the only guy capable of achieving a failure.
I asked politely, my friend. I'm suggesting your guess, leveled as an authoritative position, is misleading. Its you, taking my story away from me, asserting your narrative based strictly on preference. It's a narrative I disagree with, so I'm not inclined to let it slide.
It's crass to do this. There are many, MANY other ways you could have decided to open this subject. You could have related to YOUR experience, you could have asked for explanation of how these problems are perceived or surmounted. You could have simply expressed skepticism about the team size.
But you didn't. Here we are, on the wrong foot.
> I don't want to get into some argument with someone who feels he is the only guy capable of achieving a failure.
Then worry not! I am not: a guy, the only one capable of achieving, nor do I consider the product that made me a millionaire a failure.
The non-author comment is well taken though, as I find Clojure development to have a much higher thinking-to-coding ratio, so when a solution gets translated from the author's head to code, much of the complexity can be hidden due to the conciseness and lack of verbosity. I didn't really find that to be a problem in Perl, however, with the exception of perhaps the (over/ab)use of complicated regular expressions.
Also, this notion of code should be readable by everyone (not involved with the project) is bullshit. Learn the notation once, make use of it forever. All that is needed to do to find the grain of the problem, and cut along it (and REPL helps a lot with that). Contrarily, you can write code assuming no background - which leads to Java like code, where everything must be repeated.
Perl is a write-only context-dependent (with side-effects too) language, because Larry Wall thinks computer languages should be like human languages.
- stack traces - jvm - lein is pretty slow
but the upsides
- immutable data structures - lisp/macros - repl (you can send code from your editor to the repl) - community - great open source web app libraries (for me, anyway)
outweigh the downsides.
I encourage everyone here to try clojure at some point, it might just become your favorite language.
When I learned clojure two years ago, I didn't have any experience with java or the jvm, so I had to sort of learn where the boundary was.
After that initial hurdle however, I now use java interop quite a bit.
With Clojure, it often takes 10 lines or less to solve problems that take 100s of lines of Java. This encourages a lot more experimentation and refactoring in my experience.
Also, since you're always working in the REPL, you can always run the code after you change it and see that it's doing what you want immediately.
I think this is one of those you can choose 2 situations.
If you know C#/Java then learn C++ or vice versa and it'll affect the way you think about programming.
Or move on to different paradigms...
Go outside of the statically typed OO world and try out the dynamically typed OO languages like ruby or python.
Even better, learn a weakly typed procedural language like C ( lingua franca for CS ).
Or choose an architecture and learn some assembly. Now that will affect the way you think about programming.
Move out of onto a different domain altogether and try one of the flavors of a declarative language like SQL.
Learning a bit here and there of many different languages and paradigms will affect the way you think. Clojure isn't special.
You can even just stay within a framework and try to get a deep understanding of .Net language/IL/Framework Libraries/Runtime stack. Then you get into the debate of breadth vs depth. Should you learn one language/framework/paradigm exceptionally well or should you learn a bit about a lot of a lot languages/paradigms?
Learn Prolog.
... and then implement Prolog in Lisp so you can write Prolog on your Lisp code as well (this has been done many times)
Especially with CLP(FD) and other Prolog constraints.