Clojure will affect the way you think about programming
eli.thegreenplace.net
eli.thegreenplace.net
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.)
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.
Many 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...
Dynamically typed. As a dynamic language Clojure is less capable than many other Lisp implementations/languages. See for example 'late binding'. In Clojure you either need to define a function before its use or need declare it. Not that 'dynamic'. In a typical Lisp dialect the order of definitions makes much less a difference, since functions can be called late-bound. For an interpreter-based implementation, it makes no difference at all.
> It promotes combinations of built-in data structures (lists, maps, vectors) over objects
An aspect I don't like. This makes debugging of larger systems more difficult, since maps are maps. Whereas objects have a type tag built in, have a list of allowed fields, etc. etc. They simply have more more explicit and standardized structure to them.
> Some built-in features like reducers and transducers rely heavily on composing higher order functions for transforming other functions.
Which makes code complex, since transducers are a mildly complex mechanism.
> It encourages REPL-based development
In relatively weak form. Basic features of Lisp need to be added to do simple tasks: like 'instrumenting' code to debug it, since it lacks an interpreter or more capable compiler support. REPLs with actual interactive error handling are not standard. Interactive Lisp compilers like the one from SBCL give much more information and warnings.
Thus the Lisp development features are at best only medium-level.
> Historically, Lisp programmers weren't the biggest proponents of OOP
Funky, people from the Lisp community helped to shape OOP and explored much of it. From guys like Hewitt who developed the actors paradigm in the early 70s, Minsky who developed the frame theory mid 70s and inspired a whole range of OO programming in Lisp, to Howard Cannon who developed Flavors in 1979 with flexible mixins and multiple inheritance used to implement the object-oriented operating system parts of the MIT Lisp Machine, to Lieberman who developed OOP with prototypes mid 80s, to Kiczales who worked a decade on OO in Lisp (LOOPS, CLOS, Meta-object Programming, Object-oriented Protocols, Meta-level architectures, early Aspect oriented programming, ...).
Actually objects in the early Lisp were simply symbols with their property lists. These property lists could hold both normal attributes and also functions.
You can create explicit and standardised structures for maps in Clojure, or indeed any other data structure:
(s/def :foo.student/name string?)
(s/def :foo.student/score nat-int?)
(s/def :foo/student (s/keys :req [:foo.student/name :foo.student/score]))
(def example-student
#:foo.student{:name "Alice", :score 30})
However, Clojure takes a somewhat divergent philosophy, as it encourages writing schema for individual fields, rather than a schema for a grouping of fields (such as an object).For example:
{:foo.person/name "Alice"
:foo.student/id "xyz123456"}
The namespaces of each keyword are different, but they're grouped in the same map as they happen to refer to the same entity.So rather than operating on a fixed type like `Student`, a function would request that it requires a data structure that has a person's name and a student ID:
(s/fdef enrol
:args (s/cat :student (s/keys :req [:foo.person/name :foo.student/id])))
We're still validating (albeit dynamically), but we can be more flexible in what data we ask for. This ties in with Clojure's idea of simplicity; a function shouldn't know about data it doesn't intend to use.Then there are defstruct and deftype. So it has maps, struct-maps, records, deftypes, Java classes, ... a whole bunch.
'simplicity'?
> This ties in with Clojure's idea of simplicity; a function shouldn't know about data it doesn't intend to use.
I would use a class for that, since something like CLOS supports multiple-inheritance and all the necessary mechanisms for mixins.
Finding these methods then is supported by the organization in generic functions and hierarchical classes.
defstruct has been deprecated for years. deftype and Java classes are primarily for JVM interop and language extensions.
Outside of calling Java libraries there are just maps, which you're going to be using 95% of the time, and records, which are maps with efficient polymorphism.
> 'simplicity'?
In Clojure parlance, "simplicity" refers to interconnectedness. So a box of tools is "simple", because they're not connected, whereas a Swiss army knife would be "complex", even if it might be easier for a novice to use.
Having a bunch of similar tools isn't complex, from Clojure's point of view, as long as they're separate. It may make things more difficult to learn, but that's another problem entirely :)
> I would use a class for that, since something like CLOS supports multiple-inheritance and all the necessary mechanisms for mixins.
Doesn't that mean you'd need a separate class for each way you access the data if you wanted to type that? My knowledge of CLOS is, I'm afraid, rudimentary, but can you say to a method: take as as argument an object that has the "id" field from the "Student" class, and the "email" field from the "User" class. Or would you have to explicitly pre-define mixins like HasStudentId and HasUserEmail?
Clojure tends to redefine words describing concepts with a new or restricted meaning. Typically a general concept like 'simplicity' would have more than one dimension.
> take as as argument an object that has the "id" field from the "Student" class, and the "email" field from the "User" class. Or would you have to explicitly pre-define mixins like HasStudentId and HasUserEmail?
I would not do this at all. Having slots or not are low-level artefacts and writing methods which are fine-grained to slots of an objects don't expose the domain-level.
I would use methods for domain level classes which have the necessary slots either local or inherited.
Sure, but that happens all the time in programming. When we talk about "objects" we don't mean "a material thing that can be seen and touched".
> I would not do this at all. Having slots or not are low-level artefacts and writing methods which are fine-grained to slots of an objects don't expose the domain-level.
I guess because it's not at all idiomatic, even in an object system as rich as CLOS.
But in Clojure it is idiomatic, and to my mind it leads to a more precise definition of what a function wants. We don't say, "this is a method that operates on a Student object"; we say, "this is a function that takes data that includes a student's email and enrolment date".
Again, I hasten to add that my experience with CLOS is virtually non-existent, but compared to the OOP languages I am familiar with, Clojure has a far richer way of describing the structure of data.
Clojure's specs, adhoc or otherwise, certainly might not be your cup of tea. Clojure really pushes the idea of separation and isolation, and in my view you need to buy into that philosophy to be at all comfortable with the language.
Where I disagree with you is your assertion that maps in Clojure are always less structured than objects. I'd claim that (at least when properly spec'ed out) they provide far more structure than objects.
> It promotes combinations of built-in data structures (lists, maps, vectors) over objects
I would say that this is actually the case and this article is not the only one to say that.
You can do this using a row type, e.g. OCaml:
# let foo student =
Printf.printf "Email: %s\nEnrolment: %d" student#email student#enrolment
val foo : < email : string; enrolment : int; .. > -> unit = <fun>
The second line is the output from the OCaml REPL. Note how it automatically infers that the input is an object which includes an email and an enrolment field, with the correct types.Yes, this is totally misleading... Lisp had OOP before Common Lisp existed, for example the Flavors OOP system through the '70s. This, of course, is before C++ was even a thought in the mind of Bjarne Strostroup.
Also, interesting fact: Common Lisp was the first Object-Oriented language to get an ANSI standard.
I was quite surprised to see that this already existed, together with the "property" terminology, in McCarthy's primordial "pre-LISP" list processing system described in AN ALGEBRAIC LANGUAGE FOR THE MANIPULATION OF SYMBOLIC EXPRESSIONS. Basically any time we say that an object has properties, we are referring to that.
In those two years, it thought me about:
- Interactive programming
- Persistent data structures
- Immutability
- Code that writes code
- Functional programming
- Extendable polymorphism
- Lazy evaluation
- Eager evaluation
- Parallel computing
- Dynamic variable extent
- Recursion
- Concurrent programming
- Declarative programming
- Aspect oriented programming
- Logic programming
- Code as data
- Software transactional memory
- Generative testing
- Contracts guards
- Optional type systems
- Conditional restarts
- Monads
- Variant types
- Collection abstractions
- Array programming
- Map Reduce
- List comprehensions
- Reactive programming
- S-expressions
- Destructuring
- Pattern Matching
- Prefix operators
- Closures
- Data literals
- And more...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:
- Interactive programming
- Persistent data structures
- Immutability
- Code that writes code
- Functional programming
- Extendable polymorphism
- Lazy evaluation
- Eager evaluation
- Parallel computing
- Dynamic variable extent
- Recursion
- Concurrent programming
- Declarative programming
- Aspect oriented programming
- Logic programming
- Code as data
- Software transactional memory
- Generative testing
- Contracts guards
- Optional type systems
- Conditional restarts
- Monads
- Variant types
- Collection abstractions
- Array programming
- Map Reduce
- List comprehensions
- Reactive programming
- And more...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.
https://s3.us-east-2.amazonaws.com/photoblobs/screencast_201...
There really is no way to offer "all of clojure" as native because the ecosystem is part of what "all of clojure" offers.
I've long thought about ways of doing a "native" clojure, and have eventually come to the conclusion that it doesn't offer anything compelling.
If you want fast start up time and scripting, you can do that with node and clojurescript. Or Lumo or whatever else there is in that area.
Once running, the JVM is pretty performant in a lot of cases... Where it struggles are where all garbage collected languages struggle. If that's the pain point, you could probably bootstrap a scheme from Gambit or Chicken that did manual memory management...I struggle to imagine what that would look like in the end though. Lisp flavored C maybe.
People smarter than me have said that the Clojurescript compiler is an excellent starting point for making a compiler that emits other languages. It's over my head, but maybe it would be a worthwhile project to emit golang or something.
I believe some of the clojure-schemes that floated around briefly bootstrapped from clojurescript compiler to emit Gambit Scheme...
Without the libraries/ecosystem I don't think it would be worth it.
Unfortunately it's (intentionally) horrible for interfacing with C libraries.
> it doesn't offer anything compelling
Unless you need to interact with native libraries.
There's currently a lot of excitement about Scala Native and Kotlin Native. Those projects aren't being done just for the heck of it.
Native libraries is a good reason. Particularly GUI libraries.
I imagine Swift would be a decent target for a lisp, for app development.
GUI is a major missing area in clojure. There are some good efforts there, Seesaw targeting Swing, and fn-fx targeting JavaFX.
- 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.
That's just a non-user's perspective on the scuttlebutt, though - I don't have any actual experience with Go.
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.
1.5 months after my first line of Swift I had my app in the app store (granted I was proficient in Objective-C). 2 years since I've started learning Clojure and a couple of months since I'm working professionally with it and I'm still quite shaky.
Luckily, it's not difficult to begin - you can start hacking small programs in a couple of days, but when it comes to building large applications, that's when it becomes a high-level art form (just one of the many possible metaphors :) ).
Quite similar to C++ in this respect - easy to pick up, hard to master.
It humbled me and made me cry many times. But as with everything, if you don't give up, you level-up.
As far as the experience goes, it gives you this 'overview' experience of programming - you can mimic almost any feature from other languages and you have access to all the libs for the JVM (including C/C++ libs through JNI) plus all the javascript libs.
So in terms of libraries you can use, I dare say that Clojure(script) is the language which can access the most libs out there.
So yeah, definitely a powerful language and a powerful tool which you can go very deep into, albeit a bit tough to learn.
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.
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.
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.
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.
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.
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.
I think this is one of those you can choose 2 situations.
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.
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:
(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:
(defun vector-rest (vector)
(make-array (1- (length vector))
:displaced-to vector
:displaced-index-offset 1))
Then if it bothered you you could do something like (defun head (sequence)
(elt sequence 0))
(defun tail (sequence)
(etypecase sequence
(list (rest sequence))
(vector (vector-rest sequence))))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.
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.
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.
If you know C#/Java then learn C++ or vice versa and it'll affect the way you think about programming.
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?
My argument was that there are such a wide variety of languages/frameworks/paradigms that it makes no sense to make such a claim.
Someone from a functional background ( lets say you know scheme or ML ) would get more from learning an OO language than from learning clojure. He'd learn more from learning assembly and seeing how data moves in and out of registers than from learning clojure.
Programming is such a wide topic. We get this posts all the time. Go will expand your mind more than anything or ruby will or F# or any other flavor of the day or even python.
It all depends on who you are and what you know.
In other words "Clojure - the perfect language to expand your brain?" is a silly assertion. There is no "perfect language" because everyone's brains are different because we all come with different backgrounds and expertise.
But that's just my opinion.
If this would be common practise nobody would learn golang.
FWIW, I think most language can affect thinking - but the degree varies.
There have been plenty of people who have built successful businesses with perl too. /shrug
I'm still going to recommend people not use either language, because I've personally encountered the downsides of 'when things go wrong'; and it's been significantly worse than in other languages.
> Are we doing opinion threads now? Okay. I can do one too!
That's a bit of a flip off isn't it?
What I have to say is based on my professional experience. Don't like it? Tough. Luck. You had a different experience? fair enough. You want to call it an opinion thread? Well, you know what they say. Opinions are like assholes, everyone has one...
The whole idea of HN is to have thoughtful discussion (not to tell one another to fuck off!) That means tolerating a certain amount of provocation when you feel provoked; it's almost always unintentional, and in the rare cases when it isn't, you owe the community better than to respond in kind.
We detached this subthread from https://news.ycombinator.com/item?id=14929745 and marked it off-topic.