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 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!
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?
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.
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.
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 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.
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.
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.
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.
>> ...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).
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.
> 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.
> 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.
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.
> - 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...
> 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_.
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, typing usually helps. Hiring and training programmers to follow good practices (like not doing what grandparent claims only happens in Clojure) helps even more :)
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#...
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.
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.
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.
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.
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.
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.
> 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.
You can create explicit and standardised structures for maps in Clojure, or indeed any other data structure:
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).
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:
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.
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?
> In Clojure parlance, "simplicity" refers to interconnectedness
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.
> 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.
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.
"this is a function that takes data that includes a student's email and enrolment date"
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.
I understand that, but as I said I don't like that at all. It binds methods to low-level adhoc features and not to class based domain ontologies. There are other pattern directed invocation systems, which also support that, but I usually prefer the more systematic approach of CLOS. In the tradition of symbolic programming I want to have symbolic classes as my anchors for functionality, not adhoc patterns.
You can still create class-like ontologies with Clojure specs, but usually you don't need to.
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.
Yes, but as I said, using data structures over objects doesn't mean the data is unstructured. You can use lists, maps and vectors and still have a well defined and validated structure.
> Historically, Lisp programmers weren't the biggest proponents of OOP
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.
> Actually objects in the early Lisp were simply symbols with their property lists.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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 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.
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.
I would love to use Clojure (it's probably the only language I would consider moving to right now) but the JVM just doesn't work for me. Clojure-native that offered all of Clojure and was realistic - there was one that indirectly used Gambit Scheme at one time, and an incomplete Python version - would be awesome. Clojure that requires you to use the current Java approaches to interfacing with C is not of interest.
I've felt that way about clojure native, but what makes a language is the ecosystem, and the JVM brings a fantastic ecosystem.
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.
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.
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.
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.
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.
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.
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.
I agree with the article, but would also like to mention that it has a very steep learning curve.
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.
> 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.
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?
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.
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.
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 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.
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.
>"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.
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.
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.
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.
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.
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 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.
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 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).
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.
> 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.
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 :-)
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.
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.
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.
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:
> 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.
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 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).
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.
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?
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.)
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.
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.
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?
That is true. Heck, eating a particular meal might affect the way you think about programming. But the article title, and the original HN title, is "Clojure - the perfect language to expand your brain?" So it's asking if Clojure is better at it than other languages/paradigms.
> So it's asking if Clojure is better at it than other 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.
Our experiences with clojure are clearly different.
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...
Ai yai yai, you took this thread way into the red. Could you please resist the temptation to do that in the future?
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.
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.