Why Clojure?
blog.cleancoder.comClojure is by far the best programming language I've ever used. Rich Hickey's Sermons On The Mount changed the game of programming once and for all. With Clojure you could finally have your Lisp cake and eat it. Witness the sheer chutzpah of the guy when he basically told Ruby devs they were doing it wrong at Rails Conf in 2012 (https://www.youtube.com/watch?v=rI8tNMsozo0).
It's a lot of fun but as projects got larger and larger for me (thousands of lines, or even tens of thousands), I found the dynamic typing taking up more and more of my time. I've since moved on to statically typed systems where the compiler takes a big load off the cognitive requirements of maintaining and debugging software.
How long have you been programming professionally?
Did you have prior experience with dynamic languages? If so, how much?
Did you have prior experience with statically typed languages? If so, how much?
I'm trying to see if Clojure requires more fundamental programming intuition and experience to sustain in large projects. I work with tens of thousands of Clojure LOC, I don't feel these issues and can't relate, and love Clojure, so I'm curious to understand what context it best applies too. I wouldn't want to force it on a team that wouldn't benefit from it, so I'm interested about learning these aspects, so I'm able to recognize in what context it would make sense for me to influence a team to adopt it or not.
Thanks.
12ish years (more as student & hobbyist), lots (mainly python, javascript and clojure), lots (mainly Java and C++, sadly inly got to tinker with better type systems). I consider myself a clojure developer and have been using it on and off since summer 09. Im using it for a large project now.
And yet... I agree with GP. I love clojure, but over time I’m becoming less and less sold as dynamic typing. Spec helps, a little. Property-based generative tests (especially when used with spec) helps a little too. Neither are a replacement for proper static types, though, especially an ML-esque type system with type inference. Bonus points if spec validation could pass type data to the compiler/type inference (eg the code path after validation can assume that the data is of the the types described in the spec).
I dream of a statically typed Clojure with type inference, spec-inferred types and optional dynamic typing for REPL experimentation and glue code.
Very interesting, and thanks for answering.
Could I ask what kind of project it is? I wonder if I'm just lucky that Clojure fits perfectly my use case, which is mostly a set of distributed systems of all kinds. So while as a whole there's tens of thousands of LOC. The components have very strong boundaries being as it's a set of services assembled together through RPC, PubSubs and DBs. Maybe that alleviate the lack of a static type checker.
We've adopted Clojure about 3 years ago, team of 10. We've had a few people leave and join throughout. Only one person knew Clojure beforehand. Our stack is about 50% Clojure, 40% Java and 10% Scala. Of all three, Clojure has given us the least issues, has been pretty easy to maintain and generally has fewer defects. Java tend to have the most bugs, almost always related to some shared state. Scala I find the hardest to extend and maintain, but the code base we have for it I think does Scala wrong, it's like the worst mix of OOP and FP.
> I dream of a statically typed Clojure with type inference, spec-inferred types and optional dynamic typing for REPL experimentation and glue code
That's pretty much exactly core.typed: https://blog.ambrosebs.com/2018/09/20/towards-typed-clj.html
That said, the project never managed to get more contributors.
If you're looking for a typed Lisp, I've been keeping my eyes out on Carp: https://github.com/carp-lang/Carp
I’ve written a lot of different Clojure projects over the years. Some large, some small. Back in 2013–2015 I ran a startup entirely on Clojure(script). My current project is an automation service for cryptocurrency trading bots (that is, all the infrastructure, automation, configuration, dashboard etc for running a bot, but the bot strategy and signals are up to the user — hence automation tool, not bot). It’s a large system with multiple services (some running in different datacenters too). Backend is Clojure (based on duct) and frontend is Clojurescript with re-frame.
Don’t get me wrong, if I restarted the project again from scratch, I’d choose the same setup (or a very similar one). I love Clojure and am very productive in it, but that doesn’t mean I don’t think it could be better still.
> That's pretty much exactly core.typed
I haven’t looked at it in a couple of years, maybe its changed, but when I did, it didn’t really do it for me. It’s still a separate tool that lives separate from Clojure itself and it felt very “heavy”. In my personal opinion and experience, having certain things as separate entities (decomplected as Rich would say) isn’t always a good thing and leads to an inferior thing. I’ve played around with many programming languages in my time (I’m a bit of an enthusiast, I guess. I like trying out languages that are very different from what I already know – that’s how I originally got into Clojure) and it seems like a common theme. The closer a feature is to the compiler/runtime, the better it works and seamless it is over all. Another Clojure example is the limitations core.async has, because its an external library: things like <! cannot be placed inside functions or the go macro can’t see it to transform it as macros cannot look inside function calls. I’ve also encountered an exception recently where the stacktrace only showed core.async and clojure.core code, not a single stack frame referenced MY source files. These problems are hard to solve as an external library.
> If you’re looking for a typed Lisp
I’m not. I like Clojure’s particular mix of sensible syntax, immutability, sequence abstraction and general way of doing things. Other Lisps I’ve looked at don’t have the same emphasis on these things as Clojure does, so I don’t want another Lisp, I want a language that makes the exact same decisions and tradeoffs as Clojure, except on dynamic vs static types (and actually useful error messages). Maybe one day I’ll give it a try, I certainly don’t expect Cognitect to change their language because of what my preferences are.
It's not as simple as that. There are no typed Lisps, even though Lisps have been around since the 60s. That's not just coincidental in my opinion. The closest to a typed Lisp are gradual typed Lisp, like Typed Racket, and that is very similar to how Core.typed does it. There is Shen as well. Carp is the first strongly typed Lisp I'm seeing, and it is experimental and might never take off.
The issue is how would type definitions be introduced, and what kind of types would be most appropriate? As you said yourself, the way core.typed did it felt too "heavy". Yet it isn't clear how to make a more lightweight variant for a Lisp language such as Clojure without rendering the types worthless.
The first, and one of the biggest issue in my mind, is that what everyone loves about Clojure is the data-oriented style. In that style, you represents entities and their relationships using heterogeneous collections. That's where in Clojure you model your domain with Maps, Lists, Vectors, Sets, etc. It is awesome, but no one has figured out a non "heavy" way to statically type it. All methods I know of have bad programmer ergonomics. In effect, adding types back to it almost kills the data-oriented style, and it ends up feeling a lot like modeling with Classes instead. See Haskell's wiki section on this problem: https://wiki.haskell.org/Heterogenous_collections they haven't solved it, and have multiple ways to possibly handle the scenario, and non are ideal.
> I want a language that makes the exact same decisions and tradeoffs as Clojure, except on dynamic vs static types
I would too, but with the caveat that the development experience would be the same, and the programming ergonomics and styles would be retained. And this, I'm afraid, is an open problem that no one has solved yet. It's not just a case of personal preference. Having a language which has the pros of Clojure and the pros of static types, without the cons of static types is hard. That's why for now, you need to choose one or the other.
This has been my experience as well. I have a couple of Clojure(Script) applications that are approximately 3-5K lines each and those have been a pleasure too work on. However, my latest project is now pushing past 20K lines and the mental load has jumped exponentially. There is a much greater need for spec, asserts, type hints, and comments just to keep everything straight.
That's interesting do you have any idea why the mental load jumped? would a static analysis tool working with your type hints help?
Or is there many things to consider at once in the system instead of many individual things?
The biggest issue I keep running into is just the concept of data "shape". I love that clojure gives you so much freedom but it can be quite the footgun in a large system because you see that a function expects a map with keys :foo, :bar, and :baz but what are the values for those keys? Spec helps a little bit here for primitive values but for complex nested structures (e.g. {:a [{:b [1 2]} {:c "bar"}]}), it doesn't do much. So, as data moves through the system, and as the system grows it has become increasingly difficult to track the mutations to the underlying structures.
I do think that a static analysis tool would be of some help. Some sort of tooling to better handle tree structures would be very handy. I often find myself being off-by-one level with get-in calls on tree data (E.g. (get-in m [:a :b :d]) where m is {:a {:b {:c {:d 1}}}}), which is annoying because the NPE gets thrown 3 function calls up the stack.
I'm late to the thread, but I've felt the same pain and wrote a library to help deal with the cognitive load of working on "shapes" of data [1].
Totally not saying "you're holding it wrong", but maybe once you're more than a few couple levels deep into a nested map it's time to look at an in memory db like [Datascript](https://github.com/tonsky/datascript)? Actual Datalog queries can replace get-in vectors growing out of hand, and you also get better mutations with transactions.
Oh man this talk... so incredibly good. The principle of simplicity vs easy is what drew me to Elixir, and what I'm starting to dislike about Rust.
In Erlang/Elixir, you have patterns, and interfaces which are fairly low level, but provide meaningful abstractions over common goals... I.e. we have OTP. OTP is fairly simple, and that doesn't mean it's easy, it's really not, but it can be simple once you're familiar with it.
Rust, on the other hand, is easy to get started with, the core of the language is fairly small (and relatively simple) but that's where the complexity starts to creep in.
It's so small, I have to constantly reach for a crate for common goals... There are tons, and they're fucking awesome, BUT they're easy, not simple, and my application's complexity grows exponentially relative to my familiarity each time I grab another crate.
I think it's easy ;) to say the Rust ecosystem has to contain the complexity it does, and making it easy is the best we can do, but with great challenges comes great reward... Batteries not included, yes, but at least you could know the size (spec) required for the ones you need? That's probably a shitty metaphor but I tried...
...
One more thought on this tirade, when Rich Hickey casually says that engineers should be working to remove complexity from the business... That hit me right in the feels. Too often do we get complex business decisions/goals and just accept them. We need to push back and help them simplify their goals... too often I've gotten some crazy request, where instead of implementing it, I pushed back to get to the root of the problem and ended up with something wildly simpler and better for both parties. When we push back, it shouldn't be to make something easy, it should be to make something simple. You can scale simplicity almost infinitely, but complexity will eventually come crashing down.
More on topic... Thanks for posting this talk, it and the article have inspired me, I'm gonna go try to write a bit of Clojure today. You should write some Elixir, or maybe LFE (Lisp Flavored Erlang), I think you'd quite like BEAM :)
> Too often do we get complex business decisions/goals and just accept them.
At my last job the client reps were notorious for saying “yes” to every client request and other devs were notorious for implementing those requests without question. I really tried hard to push back and find out what the client was trying to achieve. So many times after a brief discussion I would be able to inform them that what they wanted was already possible or doable with way less effort and expense. Sure, we didn’t get to bill them as much, but I’d much rather solve their pain points without adding code (especially when the code has already been written) than needlessly charging them.
FYI, Clojure can run on the beam: https://github.com/clojerl/clojerl
> by far the best programming language I've ever used
Would love to hear why? What is that make Clojure such a good experience for you?
Not the grandparent, but I realized the other day that I'm at nine years of clojure, so...
What's made clojure so great, imo, is its unicorn status as a principled-yet-practical language. That "principled" part is not worthless---it means that a lot of great minds are drawn to it. Before react took over the world, clojure folks were already taking steps in that direction.
A lot of other things. The "sequence" as a core abstraction is very powerful. Immutable data structures by default make functional programming perfomant and efficient.
Speaking of data structures, data structure literals have spoiled me for other languages. After Java, especially.
And none of this mentions clojure's lispiness. The ease of metaprogramming has allowed the community to build some of the best tooling out there, between CIDER for emacs, and Figwheel for the browser---oh, did I forget to mention clojurescript? Being able to reuse code on the front and backend is great for web applications.
Clojure isn't "everything." I think I'd still benefit from learning Haskell, APL, and Forth, and I wouldn't mind knowing Ruby and js a bit better. And I'll probably be dragged back to Python and R if I keep doing maths.
But if someone asked me which single language would probably do the most for them professionally, I'd say clojure. It's the language people start startups so they can use it.
I wish the last bit was true. Clojure jobs are very thin on the ground even here in startup London.
What is GUI programming like in Clojure? What libraries exist, and what paradigms are used? E.g. is it more like React or is it more like Gtk/Qt?
There's a really good UI framework for Clojure (not ClojureScript) called seesaw. If I recall correctly it was swing, but it's a very nice data focused, functional library. It really works well with the Clojure philosophy and interactive development. It's also VERY complete.
There are downsides, however. The UIs that it creates definitely look like Swing apps. I.e. ugly at least on Linux. Also, the up to date documentation is difficult to find, I'll like to it here when I get home. The GitHub-linked docs work fine, but they do miss a few features.
Seesaw/swing are the old stable solution. The latest library is cljfx built on JavaFX. The developer is very responsive and open about it's development
Check out the examples/mini-tutorial: https://github.com/cljfx/cljfx
It's all React-like. You have a state atom and a GUI map data structure (which I think is equivalent to your DOM in React..) and then you hook up events that update the state and blah blah. It's all very clean and easy to read/use. The underlying JavaFX is also great for an OO GUI library so it's not gross to dive into if you need it
For frontend Reagent (React but with just Clojure functions and data structures. No classes, no jsx, just one language and immutable data.), and re-frame which does state management and event dispatching better than the competition. Redux is a poor poor imitation.
For web frontend (ClojureScript) there's reagent and re-frame, the Clojure versions of React and Redux.
There is a swing example on the getting started page (or there was a few years ago). It was a one liner to have a Swing pop up say "hello world" or something like that. I'm not sure how easy it is to write actual apps that way.
Dynamic and functional. Lazy evaluation. Real immutable data structures. Core.async. A modern lisp not constrained by the exclusive use of parens, ie. [] for vectors and {} for sets & maps as with JS, Python and Ruby. Real REPL-driven development. Macros. Spacemacs with CIDER. Polymorphism which beats OOP at its own game. Massive Java ecosystem at your fingertips plus 25000 pure Clojure libraries and last, but by no means least - a rock 'n roll BDFL with a mullet.
Not original poster, but my take is:
- Immutable data-structures with concise literals for lists, vectors, maps, and sets. Having pure functions and immutable data-structures makes code easier to reason about, easier to test, and thread-safe.
(But clojure doesn't "force" you to be pure. The idea is that you write as much of your code in pure functions as you can, and push the IO and impure parts to the extremities. It's very pragmatic in this way, and you can get real work done.)
- Simple syntax:
While the syntax may be intimidating at first and arithmetic looks weird to untrained eye, once you realize that everything is a function (or a special form that also looks just like a function), the very light syntax and consistency feels amazing/refreshing.
- Macro system:
This is tied to the previous point. Since all your code is technically a "list", and you have something akin to a pre-processor where you have the full library of clojure functions to manipulate data. But in this pre-processing phase, your data is the code. (the code is a list). Now you can dynamically re-arrange or re-write code. Lookup homoiconicty and learn about the kinds of things you can do in macros that can't be done in other languages.
- Capabilitis for general programming, abstractions, code re-use etc.
- Performance is quite good. It's often nearly as fast as java, yet your codebase might be 10x smaller because the language is so expressive.
- Concise/expressiveness. Clojure comes with a nice built-in library with generic functions that you re-use again and again. Give a programmer a few dozen of these functions and it's amazing what can be composed to solve many problems succinctly and elegantly.
- There are surely other benefits, but the last one I'll leave with is hard to explain unless you have felt it before. It's REPL driven development.
Clojure comes with some seriously awesome REPLs (i.e. a shell for interactive tinkering with the language). Other languages may have some form of REPL, but no other language in my experience has come close to the feel you get in a clojure REPL. I can best explain it as a freedom of very light-weight experimentation that you use to write your code. It's a playground for writing functions with very quick feedback to see if your code will work or not. One factor that makes the clojure repl experience so nice ties back again to the succinctness and expressiveness of the language. Typing commands in the repl is painless because it's concise, not a lot to type, and then the feedback is so instant.
Somehow when I write code in python, java, javascript, or other languages, I just don't use their REPL or shell as often. It's just not quite the same as the clojure repl experience.
The above posters point on Homoiconicity is a big one, here is a quick (and silly) example for anyone unfamiliar with the term.
Take the following clojure:
Here the ( ) delimits, a list, and as the blog post says most lists are function calls. In this case the function is + and it's params are 1 and 2.(+ 1 2) => 3It means if we do this
We get an exception that 1 isn't a function...(1 + 2)However, we can tell Clojure to treat this code as data by quoting the list using the '
THis is now a list. Where the first item in the list is a number and second is a symbol + and the last item is a number.'(1 + 2) => (1 + 2)WHat if we write a function to swap the first two items in a list?
We have to quote the parameter (1 + 2) as clojure evaluates arguments to functions (mostly..), by quoting it we are saying don't evaluate, instead treat it as data.(defn swap [x] (list (second x) (first x) (last x))) (swap '(1 + 2)) => (+ 1 2)So you can see
Looks like Clojure code, even though it's a list.(+ 1 2)We can eval it:
It's inconvienent to have to remember to quote the params and call eval.(eval (swap '(1 + 2))) => 3Up steps macros, macros are evaluated before compile time and don't evaluate their arguments. So we can rewrite swap as a macro
Now we can call(defmacro swap [x] (list (second x) (first x) (last x)))
This lets us essentially extend the compiler and create DSLs specific to your domain problem. Creating new language constructs is incredibly easy.(swap (1 + 2)) => 3Is there a way to simplify the `swap` function? E.g. in JavaScript it is just
swap = (x,y,...rest) => [y,x,...rest]Why not just do that as a function though? Why use a macro?
You couldn't write it as a function, unless you pass in the 1+2 part to an outer function (macro) as either a list of arguments (1, math.add, 2) or a string (+you have an eval fn).
At that point you're emulating lisp without the elegance, and the first approach is only possible because functions are first class objects. If you were trying to rearrange an expression that had control flow or keywords in it (eg. Modify the behaviour of an if) then you would have to reify the "if" (change the original code so it had a class to represent the if not use the keyword). So you're kind of reinventing lisp by wrapping every part of your language as an object
I'm apparently too stupid for you :). Please excuse my ignorance. Why can't you do something like the below?
(defun swap (x y) (y x))
Then you can call it like:
(swap (2 3)) => (3 2)
In the example we wanted to be able to call (1 + 2) and have it evaluate to 3.
Clojure tried to evaluate it's argument to swap, and the argument was (1 + 2), which is a function call, where the function is 1 and the arguments are + and 2.(defn swap [x] (list (second x) (first x) (last x))) (swap (1 + 2)) =>Exception! 1 isn't a function.So we quoted it in the function call by putting ' in front of the list '(1 + 2):
Here, we stll didn't get 3 as our output... We got (+ 1 2), which is a list. Because the function returned a list, it didn't return code! It might look like code, but it's not code! It's a list.(swap '(1 + 2)) => (+ 1 2)So if I was to
Because what it actually runs is(+ (swap '(1 + 2)) (swap '(3 + 4))) => Crashes! Can't convert alist to a number.
Whereas with the macro(+ '(+ 1 2) '(+ 3 4))
Works because the macro gets expanded BEFORE compile time, and our swap code gets replaced out with the code the macro generates!(+ (swap (1 + 2)) (swap (3 + 4))) => 10
actually compiles as:(swap (1 + 2))
SO at runtime, that will be 3.(+ 1 2)In clojure if the reader (compiler) ever sees a list like `(2 3)` it evaluates it as a function call with the first item as the function so you'd need `'(2 3)` or `(list 2 3)` to generate a list literal (or more often `[2 3]` as a vector type in clojure).
You could do `(defn swap [the-list] ((second the-list) (first the-list)))` (which would invoke the second item as a function on the first item). It comes down to is the list a literal list as a parameter to something or is the first item a function to be invoked.
It was an admittedly simple example that was to show how easy it is in Clojure to treat code as data and data as code.
The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code.
A better example would have been the (when) macro.
In Clojure you have (cond) and (if) for conditional evaluations. If has the form
e.g.(if (cond) (when-true) (when-false))
Now what if we want to do more than 1 statement on the true path and we dont care about the false path?(if (= 1 1) (println "1 = 1") (println "uh-oh the unviverse is broken!")) => "1 = 1"
This won't work as now "also hello" is the false path.. We can use (do) to specify multiple things to be done.(if (= 1 1) (println "1 = 1") (println "also hello"))
But writing (do) is a bit of a pain, so an alternatie would be (when)(if (= 1 1) (do (println "1=1") (println "Also hello"))) 1=1 Also hello
Will print, when run,(when (= 1 1) (println "1=1") (println "Also hello"))
COuld we implement this as a function, sure? BUt we would have to quote the function calls when we pass them so they aren't evaluated and then eval them if true. You could do it, but it would be messy."1=1" "Also hello"Essentially, we want to write
But want the code that gets compiled to be:(when (= 1 1) (println "1=1") (println "Also hello"))
So we want to extend the language so we can write (when) and the compiler will write us an (if (do))(if (= 1 1) (do (println "1=1") (println "Also hello")))This is incredibly easy in Clojure. We just need to create a list where the first item in the list is `if`, the second item is the conditional we pass to the macro, the 3rd item is a sublist, of which its first item is `do` followed by the list / functions to execute when our test evaluates to true!
This is just the source code for the actual clojure core when macro. But you can see how easy this is!(defmacro when [test & body] (list 'if test (cons 'do body)))If we run the macro expansion on when we can see the code that it generates:
Another example would be the reader macro:(macroexpand '(when (= 1 1) (println "1=1") (println "Also hello"))) => (if (= 1 1) (do (println "1=1") (println "Also hello")))Say we have the nested function calls:
After you have a lot of nesting this can get difficult to read, so you have a macro ->> which is the threading macro. This takes a series of functions and threads the result of each function as the input to the next.(reduce + (filter even? (range 1 11))) => 30
The source code for this is almost as simple as (when)(->> (range 1 11) (filter even?) (reduce +))
The ` ~ and ~@ are just doing some quoting and quote splicing to determine when we want stuff evaluated.(defmacro ->> [x & forms] (loop [x x, forms forms] (if forms (let [form (first forms) threaded (if (seq? form) (with-meta `(~(first form) ~@(next form) ~x) (meta form)) (list form x))] (recur threaded (next forms))) x)))Basically, using macros you do things like control symbolic resolution time, extend the compiler to create a DSL spcific to your domain and reduce boiler plate code.
That's before you start getting into properly weird stuff like anaphoric macros.
Thank you! I think I've seen the "if" and "when" examples before, but it clicked better this time.
I've read a few Lisp books and dozens of internet blog posts, so I know about macros and why people use them without getting the full understanding which comes with actually writing code.
Because function arguments are evaluated before the function itself. In this case, that evaluation would fail because numbers do not implement IFn, and hence cannot be called.
Macro arguments are not evaluated, and so this works.
You could make it a function, and pass the arguments quoted, but it'd be more cumbersome.
I have just tried it in REPL and got this cryptic error:
cljs.user=> (defmacro swap [x] (list (second x) (first x) (last x))) #'cljs.user/swap cljs.user=> (swap (1 + 2)) Execution error (Error) at (<cljs repl>:1). 1.call is not a function
> Macro system
Macros are actually my least favourite part of Clojure. Sure, its great to have them and there are some libraries that use them to excellent effect (instaparse, Hugsql, etc), but most of the time, I prefer tools that don't use macros. You can see by comparing libraries that were made in the earlier days of Clojure versus more recent ones: the earlier ones love to use macros while the newer ones prefer functions and data. The data-first libraries are, in my opinion, easier to test and easier to build complex things on top of. If I look at a readme and see that the expected way to interact with a library is a macro, I usually look for a data-first library instead and only use the macro one if I can't find an alternative.
Macros are technically cool, you can do some interesting things with them, but I find that in most cases where they're used, they are inferior to alternatives. Its technically very cool that core.async could be implemented as macros, but I feel that core.async greatly suffers from it versus being built into the runtime: you cannot use many operations in functions called by core.async/go because the macro can't see inside function calls. Also, I've had exceptions thrown by core.async where the stack trace did not mention any of my code. That was not fun to debug.
Don't get me wrong, macros do have their place: hugsql uses macros to parse the SQL file and generate functions for you, which is awesome! But for every great macro-based library, there are many more that I wish didn't use macros.
(This is user-facing macros in a libraries' API. I have no problem with using macros internally)
I do love Clojure overall, though.
I do agree that you should prefer functions over macros. The rule of thumb I’ve heard is only use macros if they are absolutely necessary.
But having that option is better than not having the option at all IMHO.
Absolutely, I even mentioned some cases where I felt they were used to great effect. Having said that, though, in my experience, they do cause an “just build it with macros” attitude. For example, the issues I mentioned with core.async will likely never get fixed as doing so would require compiler/runtime support which is highly unlikely to happen.
I agree!
When I meet a girl, I ask her about macros and functions first.
I except to hear something like: macro < function < data.
Ever play with Erlang or Elixir? If yes, I am curious what your thoughts are on them.
I've been doing a lot of Elixir recently, and I have a fair amount of experience with Scheme and Clojure. The biggest "aha" moment for me with Elixir came when I realized that everything, just like in a LISP, was an expression.
Then, when I learned about Elixir's macro system, I thought, "wait a minute, this is LISP! This is just a LISP in Ruby's clothing in Erlang's pasture!"
That was a delight. :) My heart still belongs to the LISP family, but right now I'm working as an Elixir developer.
Yes, Elixir would be my second choice but although it has Lisp influences it doesn't capture the real magic of Lisp for me. Code as data belongs exclusively to real Lisps. Elixir also lacks vectors of its own and tends to be too niche compared with Clojure.
Can anyone give a pro/cons analysis of a ML variant versus of a Lisp variant. Let's say F# versus Clojure?
One of the general arguments in Lisp vs the world is that lisp is more concise. F# is really concise. It has datastructures as intrinsic part of the language syntax (just like Clojure, i.e in F# [|,,,|] is an array and in Clojure [...] is a vector, so more or less the same thing). Furthermore, the type inferences concierges the developer through the regular bureaucracy involved with strong type systems so effortlessly that you seldom notice it ... unless your code does not compile, in which case it's wrong somewhere and you need to fix it (which you needed to do anyway).
Ok, so for the sake of the argument, let's say the syntax is equally light for F# and Clojure, and their support for immutability is in the same ballpark. And let's remove the argument "a type system is an encumbrance" because that will only lead to situation specific debates - one side will say that massive refactorings are a pita without types, and the other side responds that in Clojure you can use data schemas and should have unit tests anyway. And those discussions never lead anywhere conclusive.
So, besides the above mentioned topics, is there anything general one can say of the distinctive advantages of these two languages in specific situations, including how their two different ecosystems and runtimes affect the calculus?
I suppose it's extremely rare to have experts who have participated in any large scale industrial developments with both of the languages, but if there are, it would be really nice to hear their opinion. And the things I listed as "outside of the discussion" might actually be critical, who knows...
My previous startup (CircleCI) was written in Clojure, my current one (Darklang) is written in OCaml. I decided not to use Clojure again because it's not statically typed, and my number one frustration when I coded in the CircleCI codebase was that it was very very hard to know what shape a value had, and whether it could be null.
OCaml certainly has a lot of flaws, and is not nearly as "nice" a language as clojure, but the productivity of static typing (in the statically typed functional language sense, not the C++/Java sense) is huge. Knowing you can do a big refactor and the type system has your back is massive.
We sponsored core.typed to add types to Clojure, but there were flaws at the time (they have have been fixed since), and we didn't end up sticking with it.
So Clojure I wouldn't use again. OCaml I would, despite having significant flaws (every language has significant flaws).
Thanks so much! This was exactly the sort of industrial benchmark I was after.
Am I correct in summarizing your experience as:
The possible encumbrance caused by types at prototyping phase is paid back several times when refactoring a production codebase?
Hmm, I think it's more that types make it possible to refactor without fear, both at the prototyping phase (when you refactor so much, I had 800 lines of python before that that I was afraid to touch) and later.
I have no idea what darklang is (your site has zero information), but what made you opt into OCaml out of all of the options out there? Any previous experience with it prior to this?
Ah, sorry about that. The blog is better at the moment (https://medium.com/darklang) but we're updating the site in the very near future.
OCaml was because I had experience with Haskell and didn't like it, and with Elm, which I did like. We tried it for a while as an experiment and it worked really well.
This whole subhread and this Elm mention are really interesting, thanks. Elm is a frontend language where I really understand the benefits of static more, because you're in a fairly controlled environment and your program isn't talking to a christmas tree of type system defying external services.
But for backend, the benefits of static sound attractive to me only on a program/product that is dominated by its internal business logic and has less interfaces to the unpredictable and constantly changing outside world. I think Darklang may be like this, from looking at the medium.com link?
Ghostwheel to the rescue
(>defn my-fun [a b] [int? -> int?])
I haven't seen anything to like in Clojure spec. Maybe I don't get it, but i really dont get it.
Great question as I'm trying to make that decision myself. I love Lisp in general and the JVM is great and ubiquitous, so Clojure seems good. The reality is that every time I try to learn it, I get stuck learning leinegen, Emacs...etc, which is more than I have to deal with when using Python, so I skip it. Also, it seems like you get a .jar file and have to run it in conjunction with something like "Java -jar clojurefile.clj". I know that isn't too bad, but once I installed F#, I just put the compiler "fsc.exe" and the REPL/interpreter "fsi.exe" in my path and it is pretty simple now. I normally have an fsi window open for testing and write my full code in Notepad++ and then feed that to fsc.exe when I'm done and I get a little executable that is ~5kb. As long as a coworker has the .net runtime on their computer I can give that to them no problem. I'm sure Clojure can do something similar, but as I'm not a Java/JVM person I have no clue how.
For learning Clojure, I highly recommend Oakes' Nightcode Editor/IDE. It integrates Leiningen and Boot, and has everything ready to just code and push "run".
It also intigrates Shaun's awesome Parinfer parentheses management system, which adjusts your parentheses according to your indentation. Think Python rules: Put a thing inside another thing by just tabbing it over.
Thanks! I have the same feeling - the ecosystem with F# feels more robust and production ready. But it might be just because I've never been a Java guy and the things that seem obscure to me in Clojure are trivial to Java people.
I think both are very production worthy as far as reliable code running in production.
Check out the success stories on the Clojure site. One of the biggest ones to me is Walmart using it for Black Friday e-commerce. That is a good indicator to me. They've had good success stories with F# as well, but C# uses F# as a research language and the best bits are kind of bolted back onto C#. It keeps C# as less painful than Java, so less devs make the jump. Honestly, the future for both languages isn't 100% secure. I just wish both languages got more love.
I'm in the same boat as you. Clojure seems to really be meant for someone who already has a few years of Java experience. There are plenty of examples where someone dips into Java and I'm totally lost.
You don't actually need to know anything about Java to be productive in Clojure. At least that was my own experience.
I've been told that before and respect your experience even if it is very different than mine.
Let's talk about writing code in Python. You write a script and either run it through your IDE or via the command line via Python pythonscript.py. With D, I write some code and feed it through the compiler like dmd scriptname.d.
With Clojure in addition to all the cruft I have to learn (Emacs, leinegen...etc), I still have to know something of Java. One of the big selling points is integration. If I know zero Java, how am I supposed to make use of the various classes? Even if I largely stick to just Clojure, how does one interpret the Java stack traces? What is the best method for running via the JVM? How do I performance tune the JVM? How do I use GUI frameworks that are largely all OO Java?
You can run Clojure without Leiningen and you definitely do not need to learn emacs. I use vim, but Cursive (IntelliJ plugin) is IMHO the most beginner friendly option available (other good options include Nightlight, Atom with clojure olugin, VS Code with plugin). You can run Clojure without learning Leiningen with many of these too.
How does one not use leiningen? Do you just substitute lein for the other build tool provided by cognitect? All the tutorials I've seen have you jack-in to a lein REPL. I guess I could just use the jar file from the download directly as my REPL? I wish more tutorials started that way so I could learn without all the tools that I don't need at this point.
Thanks for helping point this out. Cursive has a trial license and is pretty cheap, but it is still annoying to get a license to something you might not need in my organization if others aren't already using it (probably easier to get visual studio for over $1k than $100 cursive license). VS Code asked me for the leiningen REPL path and I just gave up at that point for the night. I wish the Clojure download zip came with a very simple IDE like Dr. Racket or the GroovyConsole with line wrap, parenthesis matching/highlighting and a pane for the file and REPL and some pre- loaded tutorials. I would have no excuses.
If don't like or do not want InteliJ with Cursive or vanilla Emacs or VSCode, you can still use Vim, Eclipse, Atom, Spacemacs. The days when Clojure could be used only in Emacs are long gone.
Here are instructions for running clojure directly: JVM, Clojure.jar and nothing else:
https://clojure.org/guides/getting_started#_other_ways_to_ru...
You can also use boot, or if you’re using clojurescript, there are other non-leiningen options too. But really, leiningen is easy — you only need to know where in the project file to put dependencies (if you’ve ever used any other build tool, you can do this) and then run `lein repl` or `lein run` or `lein test` and to package a jar: `lein uberjar`. You don’t need to know anything else to get started.
Cursive is free for non-commercial use. If you just want a simple editor to get started, check out Nightcode or Nightlight.
Honestly ml feels like typed (basic) lisp excluding parenthesis.
So I think it does come down to if you like static types or not.
The response to "but is it slow" is pretty disappointingly bad.
> No. Clojure is not slow. Oh, look, it’s not C. It’s not assembler. If nanoseconds are your concern than you probably don’t want Clojure in your innermost loops. You also probably don’t want Java, or C#. But 99.9% of the software we write nowadays has no need of nanosecond performance. I’ve built a real time, GUI based, animated space war game using Clojure. I could keep the frame rates up in the high 20s even with hundreds of objects on the screen. Clojure is not slow.
If you're going to respond to this question at least provide some comparison maybe.
But otherwise that anecdote of "I could keep the frame rates up in the high 20s even with hundreds of objects on the screen." absolutely screams extremely slow. High 20s FPS with hundreds of objects is incredibly bad on the surface of things. Maybe it's closures fault, maybe it isn't and is instead the fault of whatever was doing the drawing. But it's not a good look either way if that's the only data point that can be provided.
Similarly the IDE/dynamic type question seems to have missed the main downside of dynamic languages - lack of good IDE & other tooling support. How well are things auto-completed for me? How well does the IDE warn I've made a type error before I've gone through a long compile & test iteration loop? How well do linters or other things work, or is there some aspect of LISP that means this is just not an issue like it is in other languages?
The biggest thing keeping me from ever seriously learning Clojure is the JVM. Slow startup time means I'd never use Clojure for "scripts", and I certainly don't want to have to manage the JVM in production scenarios, so when would I use Clojure? If there was a native version that could produce static binaries like Go/Nim/Rust I'd be much more interested to learn it.
I found managing production JVM is amazing. In fact, little other runtimes come close in my opinion. What don't you like about it?
For scripting, there's a lot of ways to script efficiently with Clojure, it's actually great for it as well. So, for most my scripts, I don't really mind waiting 1 second for them to start. That said, if you wanted them to start faster, using ClojureScript takes you under 100ms. And using Joker (https://github.com/candid82/joker) takes you under 50ms.
When I need even faster startup for scripts and also need performant runtime, I've been using GraalVM to compile to native binaries, those start in under 10ms, and execute much faster than ClojureScript and Joker. That's a good strategy if you want to write things like a grep tool, or ls, or a linter, code formater, etc.
By contrast, one principal reason I started learning Clojure was ClojureScript. Making stuff that can run in the browser or as a browser extension is awesome for cross-platform compatibility.
If you like "functional and runs on the JVM" you might like ScalaJS too. It's a mature transpiler, and the community has rallied to ensure that all the important libraries compile both to the JVM and to JS.
Lately GraalVM can be used to compile a native binary, since Clojure compiles down to Java bytecode.
https://www.innoq.com/en/blog/native-clojure-and-graalvm/
Sometimes the trade-off of slow startup vs. having a JIT'ed VM is better, since you get a lot more performance, if you're working w/ long-lived processes.
> I certainly don't want to have to manage the JVM
Of all the things I’m unfortunate enough to have to manage in production, the JVM has never caused me any problems.
I mean, yeah, I’d like static binaries too (graal is promising but not quite there yet), but managing the JVM has nothing to do with it (footprint and startup time are my primary reasons)
> How well does the IDE warn I've made a type error before I've gone through a long compile & test iteration loop?
This doesn't really exist in a lot of dynamic languages and especially in Common Lisp, Clojure, APL, Forth, Smalltalk...etc. You write a small piece of code and interactively run it with some data...test if for some scenarios or build actual tests if you want. When it is good, add it to your actual code and you're done. You interactively test everything. Once you get used to it, anything without a REPL is like wading through quicksand. The type error would probably be caught quickly when using the REPL. In Common Lisp, when your code errors out, it drops you into the REPL and you can patch your code while it is running. Smalltalk also has this. Again, when you've seen this, a Java app crashing seems hokey.
Clojure's startup time is very slow compared to other languages, but we balance it with fully reloadable systems both on the backend or frontend.
Otherwise the speed is almost identical to the speed of native Java and native Javascript. Except when you experience the power of lazy structures, then Clojure is faster than native code.
> How well does the IDE warn I've made a type error before I've gone through a long compile & test iteration loop? How well do linters or other things work, or is there some aspect of LISP that means this is just not an issue like it is in other languages?
Yes, Lisp makes this a non issue for the most part, because the compile and test iteration loop is instantaneous and integrated fully within your IDE/Editor
Still, you get pretty decent auto-complete and static linting warns on quite a few things. For that though you'll want to use IntelliJ with Cursive or Emacs with Cider and clj-kondo + joker flywheel linters.
Edit: Let me address the performance part of your comment as well. I think using a game with 20fps as an example was to show that it could even achieve such performance. Languages like Java, C#, Clojure, Python, Ruby are normally bad choices for games as they are not performant enough. So most games are implemented in C++ with an embedded scripting language on top. So the fact a pure Clojure game can hit 20fps with lots of on screen object is actually pretty good in this case.
In general, for idiomatic Clojure code, you should expect to be within 10% the performance of pure Java. Non idiomatic Clojure can often match Java's performance, and when it can not, you can implement the hot paths in Java and use interop very easily.
For ClojureScript, performance is pretty on par with JavaScript.
Startup times are the biggest issue, if pure Java takes 80ms, Clojure will take more around 500ms to start. The issue is that each Clojure function is a Java class needing to be loaded at startup, and the JVM is very slow at loading classes. GraalVM can be used to make native images, and those will start in around 10ms, but your code might need to be adjusted a little as the native images don't yet support all runtime features.
ClojureScript startup times are pretty on par with pure JS running on Node.
> Edit: Let me address the performance part of your comment as well. I think using a game with 20fps as an example was to show that it could even achieve such performance. Languages like Java, C#, Clojure, Python, Ruby are normally bad choices for games as they are not performant enough. So most games are implemented in C++ with an embedded scripting language on top. So the fact a pure Clojure game can hit 20fps with lots of on screen object is actually pretty good in this case.
What? A hundred on screen objects is not a lot, and 20fps is a slideshow. Pure Java games regularly do 10x that (Minecraft says hello as a simple example, and it's considered badly optimized)
A "proper" C/C++ engine you'd expect 100x (thousand objects at hundreds of FPS, or things like Factorio which are 10s of thousands of objects at 60 fps).
The anecdote performance is what I'd expect from a joke language like Rockstar. I'm sure the poor performance is not Closure's fault necessarily, and it probably can fly in the right circumstances (or maybe even in a lot of circumstances). Just the anecdote's description makes Closure sound horrifically slow.
I admit it's a weird way to convince us that something is fast. To tell us you are getting 20fps without context.
Uncle Bob didn't mention any spec about his computer, he didn't say if it's 2D rendered or 3D rendered, if he's using GPU acceleration or not. So I had a look at the code for his game, and it is using a software 2D renderer. That means no GPU acceleration, it is fully rendered on the CPU. It basically uses the default processing.org 2D renderer.
Now, I don't know what spec his computer has, but software only 2D renderers are generally pretty slow. So 20fps isn't bad.
Anyways, I agree it's a weird way to show a language's performance. Like I said, Clojure will run around 10% slower than pure Java in most cases. Which is pretty fast, since Java is arguably the most performant GC language around.
> 1. Economy of expression
Funny, this list is the same one I use as to why I'm so annoyed with Clojure right now.
I inherited a mission-critical Clojure ML library my team uses for it's primary business goals. It was written 4 years ago by a research scientist- who quit 3 years ago. We know what it's supposed to do. We know that it seems to do the job well. We just can't understand the code well enough to be certain of what it's doing or how it's doing it. If this thing breaks or stops working, we're screwed.
The problem is that the author, like most of us, found great joy in writing very few characters to express very big ideas. Clojure let him do that to an extreme degree. And I'm sure if you were sitting beside the author, with him explaining these dense expressions, you would be enlightened at the elegance and beauty of this language.
Sadly, I was not.
I'm sure Clojure is a lovely language. But it lets you write a Voynich manuscript that compiles.
This is true literally for every language out there. My team maintains a Java application from over a decade ago. It's an utterly incomprehensible mess that nobody understands, and it's incredibly difficult to make any changes to the project.
It's not the job of the language, but rather that of the developer to write clear and readable code.
This problem is addressed by using good design practices, code reviews, testing, and documentation.
Oh, we also own plenty of those in Java. That's the job- carefully lay the legacy to rest as you replace it with something better.
But at least the Java is overly verbose. It takes a while to read all that code. With the clojure stuff, I'm staring at the same line for just as long and getting no where.
I find that I have the opposite problem myself. With Clojure, the code tends to be more declarative and it tells me what it's doing. Meanwhile, Java leaks a lot of implementation details into the code, and I find it much harder to decipher the intent.
For example if I see something like (filter even? numbers) I immediately know what is being done and why. There aren't any surprises there.
Meanwhile, when I see something like:
then I have to walk through that code in my head to figure out what the intent is, and whether it's actually doing what it's meant to. On top of it I have to worry about mutability. Should a new variable like acc be used, or should the numbers list be updated in place, if it is updated in place where are all the other places that it's used, and are they being affected when I make a change, and so on.List<Integer> acc = new ArrayList<Integer>(); for (int number : numbers) { if (number % 2 == 0) acc.add(number); }So, there's a lot more to think about even in a completely trivial case such as the example here. In real world code that complexity quickly becomes overwhelming in my experience.
Clojure is not a particularly cryptic or difficult language so maybe a refresher in the language and standard library might help.
Well, what is your level of proficiency in Clojure? If it was your main language, do you think you would still struggle reading what the code does?
I've inherited code bases in different languages in the past, and generally, it's the unfamiliarity to the language that makes it harder. Especially Clojure, being so different, the unfamiliarity is even stronger.
For example, if you read the Clojure source code (https://github.com/clojure/clojure) are you similarly lost? Or when reading any random Clojure github project?
Also, is this the first time you inherit software like that? Like have you ever inherited other similarly complex piece of code in your main language? Because I have as well, and it is never easy, no matter the language and familiarity.
Not trying to attack you by the way, it may be true, but I'm trying to isolate the variable of it being written in Clojure against the rest to really get a feel of the effect Clojure has over what you are experiencing.
The clojure source code is not a good example, in my opinion. Much of it is written in a subset of clojure, since it builds itself up bit by bit and much of it is highly-optimised clojure code. I consider myself very proficient in clojure, yet I struggle reading clojure cores’s code.
My team is like an orphanage for legacy projects. By my last count, we have to maintain code in 8 different languages. Clojure is new to me, but I can pick up most languages (FP or otherwise) pretty quickly.
All the rest, my team has managed to decode the intent and flow.
> ML library (...) It was written 4 years ago by a research scientist (...)
Do you believe the language is the issue, been written by someone not trained on software engineering practices?
Would you find untested (I'm betting this code you inherited lacks tests, from experience working w/ ML research code artifacts) verbose Algol-family code any easier to understand?
You're not wrong, there are so many sins in the creation of this beast. A test would at least give me some purchase to grip the logic from.
But we own lots of legacy stuff. It's only this one that gives me a headache.
Honestly, I wish it had bugs or failed occasionally so I could justify replacing it!
Yes, agreed. That being said, there are languages (without naming names) that lend themselves to readability by others and those that don't.
You should be annoyed with the less than ideal coding practices of your former colleague not the language.
Didn't DJB famously use very small variable names when writing qmail in C? This is (obviously) not unique to C or Clojure. But it is something that is occasionally influenced the language best practices or culture, as you see in the giant names everywhere in Objective-C and the opposite in the push for minimalism in Ruby.
The names of complex functions, even with documentation, tends to be a bigger problem with scale than local variables and functions or utility functions.
Language design stuff like namespacing/modules helps provide a balance between short/descriptive and so does documentation of key/complex parts as well as how you architect the larger pieces and organize code in directories.
Additionally there's type systems, IDEs with docs/function + type signature integration, standards like JSDoc, etc that help. So it's a mix of a lot of things besides the human.
It does look alien. There's definitely a learning curve. But since you have the REPL, it can be very effective in understanding foreign code. Run some of the functions, add tracing to it, debug printlns, use with-redefs to replace functions dynamically etc. Once you understand the data schema better, add spec to the mix.
It is a very different approach and takes time to get used to.
I'm stuck on the other side: I have millions of lines of legacy Java code with high complexity. Even with all the types and very long names, understanding the code is not easy either and the 'step into/step over' buttons in the debugger are seared into my brain. Luckily IDEA can run arbitrary Java expressions during debugging, which gives you some interactivity back.
This is why I like python and C, almost impossible to layer in multiple layers of opaqueness with them. You can almost always deconstruct what the original person was trying to do, whether they were successful or not.
Hahahahah. The inheritors of my python code who are not my future self would beg to differ. I have so many opaque patterns that are vital and yet purely conventional it hurts. They will miss the intention and go off and do something else to solve a similar problem and now we have two patterns.
I work with Python in my day job. It is very possible to add tons of abstraction on top of a Python codebase. In fact, I would argue that it is very easy to do just that in Python, thanks to a combination of dynamic types and mutable state everywhere.
With Python, I find it's easy to understand what individual lines are doing, but really hard to understand what the codebase is attempting to do as a whole.
Our experiences with Python and C seem to have been very different.
A little bit off-topic, but I'm really confused that, why people on HN like Clojure and hate JavaScript? I didn't have any analytical data but it's very obvious already to anyone who read HN often. Are they possibly be the same crowd or just independent groups of people?
For me, Clojure and JavaScript (at least for modern React community) are similar when it comes to usage: Dynamic typing with Hash-map centric modeling, use functions to compose thing but mostly without monadic patterns. The methodology in the Clojure community has an official name: "It's just data". This methodology works, and have it's own pros and cons. The obvious pro is people can write generic functions without poking hundreds of nominal types, interfaces and type classes.
The only big difference I see is macro, but Clojure community also doesn't recommend writing macros unless you have to. There are also protocols and types, but they're not encouraged to poke around, it's still map-first recommended across the community.
There are very few language communities follow the "It's just data patterns". From Java to Python to ML, people mostly modeling things with fixed-property model, instead of just untyped maps, unless they have to.
For Lisps, I don't see map-centric approach is that stressed. Scheme might be still list-centric, Common Lisp is more diversed when it comes to approaches.
Why people hate JavaScript so much? And why people speak highly of Clojure? It's just about inconsistent details, or about the whole design itself? Is there any important point I missed?
I don't hate any language myself, only the codebases I have to deal with, but Clojure is very different from Javascript in its support for immutability, such that if you want to update values you have to use atoms, agents etc. This goes a long way towards making up for the lack of typing when trying to understand peoples code.
Some people also find the lack of random syntactical noise in Lisp style syntax preferable.
I don't think there's a lot of hate. It's just a quirky language that is at the heart of web application development. Based on its history, it has a lot of compromises. And not good ones.
It could be vastly improved with a proper REPL-based development (i.e. think developing within the browser dev tools, kinda, sorta), but instead we are now going the other way: Adding types and a compiler and making it harder to mis-type things, but also harder to work as productively.
I guess overall it has a few more ways on how you can shoot yourself in the foot compared to cljs ;)
Has anyone done both elm and clojure script for Web work? Which did you prefer, which had the best tooling etc?
I need to learn Web, as mucj as I don't want to, and love clojure and fp. Also figwheel looks awesome, but how hard is it to setup.
I also think maybe to really get the best out of clojure script you also need to learn react??
I'm not sure this is the case with elm.
Any other non js alternatives?
I have about two years of professional experience working with ClojureScript, and about three years professional experience working with Elm.
There’s almost no comparison.
Elm compiles much more quickly.
Elm produces much more performant code.
Elm enables you to write software that works reliably much more quickly because type errors happen at compile time, rather than at runtime like in ClojureScript.
ClojureScript’s error messages are at times incomprehensible. Elm’s error messages are best in class.
Elm is far easier to learn than ClojureScript, and you’re right — you do need to know React if you’re writing a non-trivial ClojureScript application.
Elm is criticised for not allowing a synchronous IO escape hatch. This is a pretty bad criticism I think. Being able to guarantee where effects won’t happen is a pretty great thing. Any effects work and/or JS interop can be done with ports. Ports work just fine.
Elm is also criticised for denying the user a lot of “power features” like typeclasses. I find it mildly annoying sometimes that I don’t have everything I want, but I understand the trade-off. This is to make the language more accessible to your average JavaScript developer who has never tried anything from this family of languages. And I sure as hell don’t find it annoying enough to want to drop the type-checking compiler.
Elm's FFI is a bit painful compared to Clojurescript's. With Clojurescript you can target Node, browser or mobile (with something like Re-natal), but it truly shines when you have Clojure on the back-end. Clojure runs on JVM and many people (for all the wrong reasons) don't like that.
You don't need to learn React to start using Re-agent or Re-frame.
IMO among all alt-js languages, Clojurescript is the most practical and production-ready.
Can someone explain me why they always (OK let's say almost always) use math formulas to show what you can do with a programming language ?
I'm a desktop application programmer, not a mathematician. I don't need to print the first 25 squares of integers or Fibonacci whatever. In fact I think the hardest math I did at work was using modulo to get even and odd numbers ...
Show me how you parse a csv file, how do you connect to webservices or retrieve data from a database, show me how to do a simple GUI, show me something useful for the common dev.
I'll judge your ultimate programming language on these menial tasks that comprise 99.9% of my programming time.
Extracting two columns out of a excel sheet:
(->> (d/load-workbook file "Questionnaire.xlsx") (d/select-sheet "Sheet1") (d/select-columns {:A :item :B :price}))Now we're talking. It would probably take 20+ lines of C# to do the same thing.
How do you manage errors? What happens if Questionnaire.xlsx or Sheet1 doesn't exist ?
> How do you manage errors?
It becomes unwieldy like every other language.
> What happens if Questionnaire.xlsx or Sheet1 doesn't exist ?
Runtime exception.
I've seen too much Clojure code (and this goes for every language where error handling is optional - Hi Javascript!) just skip error handling and focus on "happy path".
And as demonised as Java's checked exceptions are, they at least forced error handling to be thought of.
Or you could use: https://github.com/adambard/failjure
Ya'll underestimate the power of a programming language to create a programming language. Just replace the arrow with `ok->>`
What is 'd'?
It's an alias given to an imported module name. In Python it might be:
import really_long_name as rln rln.foo()I know that part :) What module is it referencing?
docjure https://github.com/mjul/docjure
I like the suggestion in namespace aliasing in https://stuartsierra.com/2015/05/10/clojure-namespace-aliase...:
As a general first rule, make the alias the same as the namespace name with the leading parts removed.
Keep enough trailing parts to make each alias unique. Did you know that namespace aliases can have dots in them?(ns com.example.application (:require [clojure.java.io :as io] [clojure.string :as string]))
Eliminate redundant words such as “core” and “clj” in aliases.[clojure.data.xml :as data.xml] [clojure.xml :as xml]
Then you can see immediately what `d` is...[clj-http :as http] [clj-time.core :as time] [clj-time.format :as time.format]Namely:
(You would still not know the library and might have to ask the same question, but it makes more sense.)(require '[dk.ative.docjure.spreadsheet :as spreadsheet])About `d`:
There are always exceptions. For example, some namespaces have established conventions for aliases:
[datomic.api :as d]
I'm with you. Reading a text file (line by line) is the thing I do most and what I look for first. If you can't do that in a simple way, I move on. I also look at returning datasets from SQL databases, how you can make a GUI, and a host of other practical things. I think fibonnaci is a sort of one step up from "Hello World", so in a sense it gives a little taste of the language and syntax.
I think if all you are doing is reading files line by line, reading a database dataset etc, then your choice of language is probably irrelevant. Choose whatever you prefer typing.
But here's CLojres read a file line by line and print it out:
A simple function to read a specified file line by lineC:\\foo.txt "THis is line 1 This is line 2 This is line 3" (with-open [rdr (io/reader "C:\\foo.txt")] (doall (map println (line-seq rdr)))) =>THis is line 1 This is line 2 This is line 3
To read the line by line and convert them to upper case(defn read-file-line-by-line [file-path] (with-open [rdr (io/reader file-path)] (doall (line-seq rdr)))) (read-file-line-by-line "C:\\foo.txt") => ("THis is line 1" "This is line 2" "This is line 3")(map clojure.string/upper-case (read-file-line-by-line "C:\\foo.txt")) => ("THIS IS LINE 1" "THIS IS LINE 2" "THIS IS LINE 3")I don't think this is remotely true. Compare doing file parsing in Python, Perl, C#, Java, Common Lisp, OCaml, Ada, Fortran, C++...etc.
Doing it in Python is easy...it is a bigger pain in several of the other ones. Even your Clojure examples are definitely more work than in Python. You even defined a helper function (something I've never needed to do in Python) as the default is so easy. Mind you, the Clojure isn't too bad and definitely better than some of the others I've mentioned.
To your average developer...this is indeed probably irrelevant as IO might be a small part of your codebase. I do a lot of process automation though and do a lot of IO.
Here is an example to massage/manipulate CSV data.
Stolen from the README page for a clojure CSV parsing library (https://github.com/clojure/data.csv)
Because it's lazy, this code should work regardless of how large the CSV file.(defn read-column [reader column-index] (let [data (read-csv reader)] (map #(nth % column-index) data))) (defn sum-second-column [filename] (with-open [reader (io/reader filename)] ; Read in the CSV file (streaming / lazily) (->> (read-column reader 1) ; Convert to just the first column of data. (drop 1) ; Drop the first row (the CSV header) (map #(Double/parseDouble %)) ; convert each string in this column into double (reduce + 0)))) ; sum the resultUsing the clj-http library in clojure to fetch from a REST webservice:
With the response, you can examine response headers, the body, etc.(client/get "http://example.com/resources/3" {:accept :json :query-params {"q" "foo, bar"}})What about JDBC?
Here's a few examples stolen from the doc page for the next.jdbc library (https://github.com/seancorfield/next-jdbc):
That is an example REPL session to setup the library, setup a datasource, create a table, insert a row into the table, and then select from the table. The "user=>" part is the REPL command prompt. Essentially it's one statement for each SQL query executed, and 1 or 2 statements to setup the JDBC datasource. This is tight.> clj Clojure 1.10.1 user=> (require '[next.jdbc :as jdbc]) nil user=> (def db {:dbtype "h2" :dbname "example"}) #'user/db user=> (def ds (jdbc/get-datasource db)) #'user/ds user=> (jdbc/execute! ds [" create table address ( id int auto_increment primary key, name varchar(32), email varchar(255) )"]) [#:next.jdbc{:update-count 0}] user=> (jdbc/execute! ds [" insert into address(name,email) values('John Smith','john.smith@email.org')"]) [#:next.jdbc{:update-count 1}] user=> (jdbc/execute! ds ["select * from address"]) [#:ADDRESS{:ID 1, :NAME "John Smith", :EMAIL "john.smith@email.org"}]You can see the return from the select contains clojure native data-structures. It returns a list of rows, where each row is a map of fieldName to fieldValue. You can now go to town and manipulate lists and maps to your hearts content, convert the payload to HTML or JSON or whatever you like.
I'd read the crap of an article that summarizes how any 10 languages handle these general purpose tasks.
Check out Rosetta Code: https://rosettacode.org/wiki/CSV_data_manipulation
It's a wiki, not a blog, but I've lost countless hours browsing through it and trying stuff out for myself.
Clojure is one of the better dynamic languages.
The rub however is that GHC can check the consistency of my software faster than I or my colleagues will ever be able to, and programmer time is expensive.
So that's why not Clojure.
> and programmer time is expensive
That's why you need to spend 3 years learning Haskell? I feel that's the right amount of years for an average Joe programmer to reach meaningful productivity with the language. Well, maybe I'm exaggerating, maybe it's just two years. Plus/minus another year to learn and understand compiler pragmas.
Hey, I won’t deny that it takes a relatively long time to learn Haskell to a standard where you’re comfortably writing web applications with it.
I’m not sure how that’s a rebuttal to my point though.
I run a startup. I’ve already put the time in to learn the technology. I’ve learned other technologies too. This one happens to be the one I find the cheapest (now) to write. Did it take my employees a long time to learn Haskell? Maybe! But I didn’t pay for that. They learned the technology for whatever reason, and I’m seeing far better returns on my investment than I think I would have if I hired a bunch of Clojure developers. My previous full-time gig was at a Clojure startup. Their pace is pretty atrocious compared to the pace of my current team.
Alright, jokes aside, let's not get into "measuring dicks" and argue which one is better. I know Clojure, and I know some Haskell. I love both. They both have their strengths and weaknesses. To be honest - I always find things in one that I wish existed in another and vice versa.
But your simple dismissal of Clojure (even though seem to be an educated one) has no merits. Don't feel bitter because Clojure is growing in popularity (in relative terms) and Haskell seems stagnated. Haskell is an older language and has had its ups and downs, and I think it will be fine. Clojure as well - no matter how aggressively haskellers would evangelize against it, it will continue to grow steadily. Think of it as a gateway drug into Haskell. I know people who jumped that way, and I know people who went the other way - from Haskell to Clojure.
To me, it is like using headphones - some prefer wired ones, some people enjoy wireless, even though audio quality suffers, wireless gives them a lot of freedom and might be viewed as a "more practical choice." To that though, proponents of wired might say: "what if the battery dies?". It is an endless and quite pointless debate. So let's concur: Haskell is great, and Clojure is fantastic too.
> But your simple dismissal of Clojure has no merits.
I believe it has an impact on my business' bottom-line. Saving money in business is a merit.
> Don't feel bitter because Clojure is growing in popularity and Haskell seems stagnated.
That… was an odd take. I'm not sure what gave you the impression that I would be bitter about Clojure growing in popularity (not that I think it is, anyway). I have nothing invested in Clojure not being a popular language, and the idea that Haskell has stagnated is a little hilarious.
> Haskell is great, and Clojure is fantastic too.
Again, I have never denied Clojure's merits. My position is pretty simple — I have found it cheaper to build Internet software businesses with Haskell than with Clojure.
I wonder which is more closer: 3years or "a week or two" to learn enough haskell to start writing things with it.
At least for OCaml/ReasonML it was "less than a week" for me and Haskell is probably not that much harder than another ML.
No, you cannot realistically learn Haskell in two weeks and jump into industrial Haskell codebase. I mean, it is not impossible - I just never heard such stories. If that was as easy as you think it is - Haskell would be much, much more popular. It is an amazing language. And probably the only point of criticism you can think of is exactly that: it takes quite some dedication and time to grok it.
I don’t think the only alternative to Haskell types is manually checking your program for consistency.
For one thing, Clojure has type analysis: spec. Also, as pointed out in the article, you can also write tests.
Haskellers can and do write tests.
+----------+-------+-------+ | Language | Tests | Types | +----------+-------+-------+ | Haskell | Yes | Yes | | Clojure | Yes | No | +----------+-------+-------+Clojure has something like an extendable type system called Spec. We can create our own types lol
A great example here: https://news.ycombinator.com/item?id=18776215
I am quite familiar with Spec. In my few years of professional Clojure experience, working with people who write Clojure every day, and enjoy it, and advocate using things like Spec, I have never actually seen anyone do it successfully in practice.
I understand the idealist world view is seductive — we'll be discipined, we'll write the tests, we'll make good use of Spec, etc etc.
In reality, I have never seen this happen. Humans are lazy and undisciplined, so that's what I optimise for.
We use spec a lot, using orchestra’s defn-spec combined with expound for improved error reporting.
It’s not a type system, but it’s very concise and easy to add validations to important functions. This removes the “lazy and undisciplined” part of the equation.
Basically you says if a type system is optional, people tend not to use it, because most of the times they are good without it.
I agree.
But we do use spec very successfully for complex data structure validations and example data generation.
As I said above, clojure also has a type system. So your chart is yes for both features for both languages.
I had taken it for granted that when someone says a language "has types", they mean it is statically typed as opposed to dynamically typed.
This is also made obvious by the idiomatic use of each technology.
So, no. My little chart is correct.
This is all nice and exciting until you start to 1) Debug code, the high density of clojure code means that this is really painful. 2) Read code you wrote a while back. The high density of clojure code means that this is really painful.
Anecdotally, clojure seems to have a very nice subset of the language which is easy to debug and expressive enough to solve most problems effectively and naturally.
What makes clojure exciting [for me] isn't the ability to build efficient transducers or create powerful custom syntax or build complicated value-level dispatch hierarchies (and in my experience, although they are cool and fun to use, all of these things certainly do make for code which is hard to understand and harder to debug), it's the comprehensive and easily-composable functional programming primitives, excellent built-in immutable data structures with intuitive shared interfaces, practical and (for the most part) straightforward approach to polymorphism, and sane approach to concurrency.
I find the high density makes debugging easier (up to a point) when combined with immutability. Being able to quickly stub/swap out a referentially transparent branch of the code while knowing you haven't more broadly changed behavior can be really useful. Also code density means you're more likely to be pointed to the line (or within a few lines) of where the actual problem is. This may be personal preference but I'd rather debug 1-5 dense line than 20-50 sparse lines given the equivalent code quality and functionality.
For me, the balancing factor is that there is much less code to debug or read.
looks like you don't know FP
pure functions are completely separate to every other parts of your software, they are very readable and easy to debug
* edit: don't write functions longer than a few lines
This. I've been using Clojure for a few years. Only once I had to actually use a debugger. Just out of curiosity.
High density?
you can accomplish a lot in a few lines of clojure
I played around with Closure for a while to learn its cost/benefits. It is basically the love child of Lisp, Haskell and Java. So it introduces nothing new to the world. However it is certainly an interesting mix that some developers like. And Rich Hickey is an entertaining speaker/presenter/seller of Clojure. Even if you don't care about Closure, watch his talks on YouTube.
Clojure has nothing at all to do with Haskell.
I can't think of two more opposed camps in programming.
You're right, one is a language with a data-structure-centric approach to problem solving and an emphasis on pure transformations of immutable nested trees and lazy sequences, and the other one forces you to learn the word Monad.
Edit: Joking aside, I do think you're right in terms of the ideological foundations and organizational structures of the two languages (clojure's emphasis on practicality, curation and evolution of the core library+language by a single BDFL, compared to haskell's origins in PL research and democratic design/maintainence), but in practice I think a lot of that comes out in the wash--dynamicism vs. static typing is definitely a huge difference, but there's a lot more to language design and the material practice of building software than whether your compiler reads type signatures, and I think that along those axes, especially when compared to other mainstream programing languages/ecosystems, clojure and haskell end up being pretty close.
Your flippant comment is not constructive, nor is it very accurate in my experience.
The last Clojure team I worked with decided to add “rop” to our project. The documentation in this library seems to go to great lengths to avoid using the word “monad”, despite the fact that the entire idea being encouraged by this library is monadic composition, but specialised to the Either monad. I mean come on, this library even takes its bind operator from Haskell.
These Clojure programmers were not Haskellers. They were just as indoctrinated as any other Clojurists and probably would have made the same flippant comment that you did.
And yet, here we are.
> clojure and haskell end up being pretty close.
In terms of the cost of building a software business, this has not been my experience. In my experience, the cost of building software at a scale beyond trivial is higher with Clojure than it is with Haskell.
I'm not sure I follow your complaint about "rop" (which is a library I'm not familiar with), and I suppose my jokiness obscured the point I was trying to make, so I'll try reiterating, since it seems like you're talking past it (I will also try to address the points you raise).
First, to be clear, haskell's type system is powerful and interesting and useful, and clojure's lack of good static analysis tools IMO represents a serious [comparative] deficiency in the ecosystem (and spec is obviously no substitute). I don't think that it will ever be possible for clojure to get even close to the sort of compile-time checking/guarantees that haskell offers, nor do I think clojure is particularly well-suited for the powerful higher-level abstractions that you see in e.g. lens or recursion-schemes (or, honestly, even Control.Traversable). My personal experience (both as a hobbyist and professionally) has been that there are some practical advantages from clojure's dynamism such that it probably makes sense (in a cost/benefit sense) to eschew static typing in some/many circumstances, but I really only have anecdotal evidence plus some intuition backing that up.
That aside, the point I was trying to get at was that both haskell and clojure encourage you to build complex data representations out of simple, easy to understand, composable, persistent structures, and then build up computations by composing pure transformations on them. Additionally, both languages have reasonable concurrency models/primitives, good tools for creating expressive DSLs (if that's your thing), and powerful interactive development environments. I, personally, find all of these features to be incredibly important for effectively developing software--they shape in a very real way the processes I use to plan and write and debug code--and most languages in common use today lack at least a couple of them (some languages have none of them, in fact).
Ok, thank you for clarifying.
Frankly, it’s not often I hear a rational argument like this in a typical Haskell vs Clojure debate. Usually I just hear zealotry and a refusal to address Clojure’s shortcomings, just like in the Robert Martin blog post. In fact come to think of it, many of the other comments on this thread which are replies to criticisms of Clojure are along the lines of “well, maybe you just didn’t try hard enough. Maybe you failed. It’s not the system that failed. Clojure is too perfect to fail.”
This kind of rhetoric is frighteningly similar to that used in multi-level marketing.
I won’t deny Clojure’s benefits. They are there, and I did say in another comment that Clojure is one of the most sound dynamic languages. But I don’t think everyone is honest like you were just now about its drawbacks. Robert Martin certainly isn’t honest about it.
Well, they have immutable data and persistent collections in common. Otherwise, agreed.
yes, immutable data is a core value of Clojure.
OP said no wrong, but think about it: Bitcoin also just stole the good parts from other software projects.
Really practical inventions usually gather the best ideas from a field.
A good remix is something new.
Having disagreed with Uncle Bob for years, now I'm starting to see some reasoning in his thoughts :) .
His book Clean Code is pretty good. Not in the sense that everything is agreeable in it (though some not so great programmers would benefit themselves and those around them from following it religiously) and a decent chunk of it is covered much more briefly with one chapter of The Practice of Programming but you can actually see the book as the artifact of a reasoning mind taking certain principles and articulating them. It lacks the usual polite qualifiers like "I think..." or "In my opinion..." or "Obviously not in all but in many cases generally speaking..." before every assertion which annoys some people. A fair number of his blog posts are like that too. Where Uncle Bob gets an unfair reputation is when people take things out of context from the larger works and (sometimes willfully) misinterpret them -- he doesn't say you should never ever have comments, for instance. Of course some of it is his own doing from occasional tweets that are by necessity of the medium less well thought out or conveyed.
I still disagree with most of what he says. It worries me that he might try to impose "Uncle Bob's one true way" of doing Clojure on newcomers (and he has very good reach into the OO community)
My team owns a Clojure app, amongst a lot of other apps - my understanding is the current DRI (who inherited this app once the original one left for another org) doesn't like working in it though, and that has been the experience of other developers who have been recruited to the project over the past two years. Most developers I work with on a day to day basis don't have any interest in learning Clojure, and would rather work with Scala, the JVM language most heavily used within my org.
One of my teammates also has production ClojureScript experience, and called it the worst of both worlds (Clojure and JavaScript). The primary problem is it doesn't try to abstract away the DOM, the most typically problematic part of working with JS.
Just my own encounter with it so far - I haven't had the experience of writing production Clojure myself, but from what I've seen with the syntax, I'm still happily primarily writing JS so far when I'm not writing Python/Scala/etc.
Ive been doing various methods of js dev with tons of different framworks and traspilers for 20 years, nothing is more pleasant to work with than ClojureScript and re-frame once you learn it. It's the best of all the worlds, and the DOM is very abstracted away via React. State transition is a breeze with immutable data structs. Theres only 1 language to write, no JSX ugliness.
I have had a similar experience over the past 15 years. re-frame is absolutely wonderful.
+1
I can understand some reluctance. My current position has suffered a lot of churn as devs ran away from clojure. My boss was very glad I showed up when I did.
It does require a different way of thinking. I was lucky in that I had very little experience, academic or professional, when I picked it up. I had a lot less to unlearn.
But that doesn't mean it's limited to ultra-brainy lisp weenies. We have undergrads writing clojure, never having written clojure before, in their first programming job. They do just fine.
> The primary problem is it doesn't try to abstract away the DOM
Clojurescript has heavily bought into React. No cljs programmer I know deals with the DOM, but rather uses a React-based library to do it. Personally, I use re-frame and reagent and haven’t needed to interact with the DOM myself in years. I find working in cljs a LOT more productive and pleasant than working in Javascript (which I also use a lot, although if given the chouce I’d use cljs instead).
ClojureScript doesn't try to abstract away the DOM. It really depends on the front-end library used. Reagent (and re-frame) is pretty much react with goodies and you can easily integrate other react libraries (using shadow-cljs)
Here is my "Why Clojure?" blog post: https://medium.com/@ertu.ctn/why-clojure-seriously-why-9f5e6...
been jumping into clojure lately myself. after years of js fatigue I've been dying for a functional pairing to elixir on the backend. clojure is growing on me with its nice hot code reloading and macros. (babel is basicly an overgrown macro)
Are you using Clojure or Clojurescript?
Sounds like ClojureScript since they're also saying they use Elixir on the backend.
> I saw the CARs and CDRs and CADDADDRs and thought it was all just academic baloney; interesting but not truly useful.
car and cdr are accessors for the fields of a basic data-structure; nothing academic about that. The very origin of the names is rooted in systems work, not academics.
(caddr x) provides a shorthand for (car (cdr (cdr x))), which is a pragmatic thing.
You know, like #(* % %) instead of (lambda (x) (* x x)).
i thought CAR and CDR were named after IBM (?) assembly language on an old machine.
That's right; and that's a systems programming origin, not academics.
He didn't say they are academic, he said he thought they were academic.
The most annoying phrase was "less code, less time, less...". Uncle Bob thought us to be explicit, help the reader understand the code and be verbose with the variables etc. Now he loves a Syntax that says #(25 % %)... Aaaalright, try to get a new company member understand a big code base with this massive oneliners. I doubt that this is the right language for enterprise projects.
Here's the fundamental thing to understand. Especially if you are new to Lisp:
The lines are more concise. That also means you will need more time per line to read the code.
Especially in Java, you can skip over so much boiler plate code, you kinda feel you are reading code fast.
Not so with Lisp. You need some familiarity with it and you will need to adapt to a line just having more stuff to understand in it.
Also, you can tune this using libraries as many things are a la carte.
But that's not a contradiction. He wants you to be explicit with using the language, but if we speak the same language, the standard bits can be concise.
So it's ok to say #(25 % %) because we both speak the same language and we know what it means, the intent is clear.
But it's not okay to call your variables a, b, c, because that's meaning, not syntax, and I can't understand your intent from those variables.
Massive one-liners probably fall into the latter, but I don't think he's advocating those.
"Economy of Expression. [..] It requires fewer lines. It require fewer characters. It require fewer hours. It requires fewer mental gymnastics. So, here, for your entertainment, is the program that prints the first 25 squares of integers."
(println (take 25 (map #(* % %) (range))))
And in APL[1], iota 25 is the first 25 integers, and feeding the same list into both sides of multiply squares the list, then turning it from a row to a column is tabling it. "Table the multiply-commute of the first 25 integers": ⍪×⍨⍳25
It's fewer lines, fewer characters, fewer hours, less syntax, less grammar, fewer symbols, fewer functions, less nesting, fewer mental gymnastics. Yet .. worse? Because those things you say both lead you towards codegolf, and implicitly have "fewer mental gymnastics" mean "I'm familiar with it already".>> Robert C. Martin: Smalltalk was also an image based language. Very few programmers have ever wrapped their minds around what that really meant. So, unfortunately, the language languished compared to all the text-file based languages. <<
Where is the evidence?
Back in the 1990's IBM taught many of their consultants Smalltalk, and researched what made learning Smalltalk difficult —
Smalltalk, although recognized as a good platform for rapid prototyping and software reuse, is widely regarded as difficult to learn. Unlike learning a procedural language like Pascal or C, learning Smalltalk is dominated by browsing and code comprehension. Learners of Smalltalk typically experience a long, slow start-up phase in which they become familiar with the class hierarchy and object-oriented computational model but do little meaningful work (our colleague Dave Smith calls this "climbing the Smalltalk mountain").
Programmers having to wrap their minds around the class hierarchy and OO was seen to be the problem.
Clojure is impressive. It's likely what I'd be focusing on if Elixir didn't exist.
I've been programming in Lisps on and off since the mid 80's, and I like Clojure the language, but in recent times as mostly as .Net programmer, I find the JVM and the surrounding Clojure tooling rather baroque. I find tutorials often cover Clojure/Lisp basics, but assume that you're coming from the Java world, so I had some difficulty getting what I consider basic workflows working as expected. If homoiconicity/macros/eval isn't an issue, I'm happy with F#/C# for the most part, but wouldn't mind having Clojure in the toolchest as well, but I'm not sure of the state and maintenance of Clojure.net.
You also have ClojureCLJ, although the tooling and libraries around aren't as nice as on the JVM (many libraries and even clojure.core functions directly interoperate with Java).
> Now let’s compare that to the equivalent Java program:
public class SquaresOfIntegers {
public static void main(String[] args) {
for (int i=0; i<25; i++)
System.out.println(i*i);
}
}
> This is considerably more wordy, even if you don’t count the enclosing class.First of all, Java is famously verbose. Nobody is impressed when a language is more concise than Java.
But even ignoring that, the comparison isn't representative. The enclosing class and 'main' function aren't needed for every additional fragment of code, so it probably shouldn't be counted here.
I might be damaged from years of Java, but my main imagined issue with Clojure is the lack of a component system. Call it OCaml functors or OOP classes, but instantiating a component with replaceable components as input is what I personally need for large scale programming. To make things more concrete, let's say you are writing an integration to another system using HTTP. On the surface you'll have functions like `fetch-album-information` and they will internally use a HTTP client. How do you handle TLS configuration, HTTP connection pooling, etc. and make the function testable and lifecycle-managed to allow clean shutdown? The easy answer is to parameterise the function with another function that takes care of all of that. But that function will need to come from somewhere and I imagine threading these kind of functions explicitly through your entire program will make it less than palatable. The alternative of using globals that can be replaced in testing carry all the usual problems that global variables have but seems to be what many settle with judging from Clojure code on GitHub. It seems like others are feeling this pain and have invented band-aid solutions like https://github.com/stuartsierra/component but this is something I think needs a first-class solution to encourage this kind of programming.
What do you mean "the lack of a component system"? There's Mount, Component, Integrant. The last thing any language needs is to "encourage" some kind of programming. And Clojure is just that - it has small, stable core. The rest can be done with libraries.
Why is a library a "band-aid", and why is it necessary to have such feature in the core language?
I think it’s an argument about ergonomics. Threading state throughout your control flow is bad. Implicits like in Scala are bad. Global variables are worse. Something like a Reader monad is less bad.
Amusingly, in an article about a lisp, he's got an unbalanced right parenthesis.
Now we just need to wait 40 years for him to come around to the usefulness of static types..
It just breaks my heart to see Uncle Bob seduced by a Java-family language right when C++ is getting increasingly fun. He suffered through the bad old days when C++ was only fast and powerful, and is now missing out on the good new days.
I guess he's happy. At least he isn't touting Haskell. Bon Voyage, Bob!
But somebody needs to break it to him that Lisp is not a functional language. Or, if it is, so is C++.
You can argue whether Lisp is a functional language, but Clojure certainly is. When written well, the bulk of your code will be pure functions taking and returning immutable data or passing around pure functions. https://clojure.org/about/functional_programming
Many lisps like Common Lisp are multi-paradigm (imperative, OO, functional). Some like Clojure are built on OO runtime and can call Java OO code, but in general heavily favor immutability and functional programming. So you're focusing on one sentence (that is arguably incorrect or only partly correct) and missing the intent of the article.
> that Lisp is not a functional language. Or, if it is, so is C++
You're missing the point. Everybody can write Fortran in any language.
There is nothing "Java-family" about Clojure. Merely the run-time.
> But somebody needs to break it to him that Lisp is not a functional language
Lisp is not a language at all. It's an idea. And if you grok the awesomeness of that idea you can make any kind of language using it: functional, OOP, relational, whatever.
Bob is talking about Clojure. Clojure is a functional Lisp.
Clojure isn’t “Java family” any more than anything running on top if a C runtime is C family. Clojure is also a lot more if a functional programming language than Common Lisp is.
I also disagree with you on C++. Cc+11 was a fantastic improvement, C++14 added some missing stuff — at this point I was sold and eagerly awaiting C++17 — but when 17 arrivd and when I saw what 20 adds... it has become so complex that I don’t understand it at all anymore. I used to scoff at the people who said C++ was too complex, but after ~18 years of using the language its now reached a point where I am no longer comfortable using it. I see code that uses C++17 and recently 20 and I have no idea what its doing anymore. I am no longer confident that I can write safe, non bug-ridden code in C++.