29 ms·
Six Years of Professional Clojure
- MeteorMarc 5y agoWhat build tools do you use, maven?
- tribaal 5y agoNot the author, but most clojure projects use leiningen to build and distribute projects (https://leiningen.org/ https://leiningen.org/) This seems to be the case for the author's open-source work (https://github.com/nanit/kubernetes-custom-hpa/blob/master/app/Dockerfile#L14 https://github.com/nanit/kubernetes-custom-hpa/blob/master/a...)
- finalfantasia 5y agoClojure developers tend to choose the official Clojure CLI tools [1] for new projects these days. [1] https://clojure.org/guides/deps_and_cli https://clojure.org/guides/deps_and_cli
- dgb23 5y agoI found one of the perceived weaknesses of Clojure (in this article), it being dynamically typed, is a tradeoff rather than a pure negative. But it applies that tradeoff differently than dynamic languages I know otherwise and that difference is qualitative: It enables a truly interactive way of development that keeps your mind in the code, while it is running. This is why people get addicted to Lisp, Smalltalk and similar languages. > To understand a program you must become both the machine and the program. - Epigrams in Programming, Alan Perlis Two of the big advantages of (gradually-) typed languages are communication (documentation) and robustness. These can be gained back with clojure spec and other fantastic libraries like schema and malli. What you get here goes way beyond what a strict, static type systems gets you, such as arbitrary predicate validation, freely composable schemas, automated instrumentation and property testing. You simply do not have that in a static world. These are old ideas and I think one of the most notable ones would be Eiffel with it's Design by Contract method, where you communicate pre-/post-conditions and invariants clearly. It speaks to the power of Clojure (and Lisp in general) that those are just libraries, not external tools or compiler extensions.
- vnorilo 5y agoAgreed. I feel Lisps and SmallTalk are dynamic done right. I think the other language features that you use also influence the value from dynamic or static types. For OOP style, static types are a huge asset for refactoring and laying our architecture. On the other hand, immutable data and stateless functions (as idiomatic in clojure) make them less necessary, and also work great together with interactive development.
- geokon 5y agoI've admittedly not played with spec, but can't you solve documenting interfaces by defining `defrecord`s ? You rarely really care about the actual types involved. You just want to know which fields you either need to provide or will recieve
- roenxi 5y agoSpec will give you stronger feedback than a docstring or function signature. It can tell you (in code terms, with a testable predicate) if a call to an interface wouldn't make sense. Eg, spec can warn you when an argument doesn't make sense relative to the value of a second argument. Eg, with something like (modify-inventory {:shoes 2} :shoes -3) spec could pick up that you are about to subtract 3 from 2 and have negative shoes (impossible!) well before the function is called - so you can test elsewhere in the code using spec without having to call modify-inventory or implement specialist checking methods. And a library author can pass that information up the chain without clear English documentation and using only standard parts of the language. You can't do that with defrecord, but it is effectively a form of documentation about how the arguments interact.
- dmitriid 5y agoOne thing I don't like about all articles on clojure is that basically all of them say: ah, it's just like lisp with lists `(an (example of) (a list))` with vectors `[1 2 3]` thrown in. So easy! But then you get to Clojure proper, and you run into additional syntax that either convention or functions/macros that look like additional syntax. Ok, granted, -> and ->> are easy to reason about (though they look like additional syntax). But then there's entirely ungooglable ^ that I see in code from time to time. Or the convention (?) that call methods on Java code (?) with a `.-` Or atoms defined with @ and dereferenced with * Or the { :key value } structure There's way more syntax (or things that can be perceived as syntax, especially to beginners) in Clojure than the articles pretend there is. (defn ^:export db_with [db entities] (d/db-with db (entities->clj entities))) (defn entity-db "Returns a db that entity was created from." [^Entity entity] {:pre [(de/entity? entity)]} (.-db entity)) (defn ^:after-load ^:export refresh [] (let [mount (js/document.querySelector ".mount") comp (if (editor.debug/debug?) (editor.debug/ui editor) (do (when (nil? @*post) (reset! *post (-> (.getAttribute mount "data") (edn/read-string)))) (editor *post)))] (rum/mount comp mount)))
- cr__ 5y agoNot sure how you’re supposed to find this page, but it’s pretty useful: https://clojure.org/guides/weird_characters https://clojure.org/guides/weird_characters
- dmitriid 5y agoNice! I missed it (or it didn't exist) when I last looked at Clojure a few years back
- roenxi 5y agoYou missed it, it has been there forever. But it says good and bad things about Clojure that its reference documentation is one of its weakest points. The Guide/Reference split obscures a lot of information (do I want guidance on Deps & CLI or do I want reference on Deps & CLI?) and the guides where that gem is hidden randomly mix advanced topics (eg, how to set up generative testing), beginner topics (how to read Clojure code) and library author topics (eg, Reader Conditionals). When you think about it, there is nearly no trigger to look at the guides when the information you need is there. Clojure is a weird mix of both well documented and terribly documented. All the facts are on the website, very few of them are accessible when required. The people who make it past that gauntlet are rewarded by getting to use Clojure.
- evanspa 5y agoGreat article, love Clojure. Was trying to figure out what Nanit does. Might want to consider putting a link to the Nanit homepage on your engineering page. When just typed in nanit.com and saw the baby monitor tech, I thought maybe I went to the wrong place, until I saw the logos matched. Anyway, good read, but please put a link to your home page on your engineering site, or, put a 1 liner in the opening of your blog giving context to what your company does.
- EdwardDiego 5y ago> An incoming HTTP request? it is a plain Clojure dictionary. I learned to code in Python. Loved it. Dynamically typed dicts up the wazoo! Then I learned why I prefer actual types. Because then when I read code, I don't have to read the code that populates the dicts to understand what fields exist.
- lvh 5y agoThe two are not mutually exclusive. Clojure has namespaced keywords and specs[0] to cover that. (There is also the third-party malli, which takes a slightly different appproach.) The advantage is that maps are extensible. So, you can have middleware that e.g. checks authentication and authorization, adds keys to the map, that later code can check it directly. Namespacing guarantees nobody stomps on anyone else's feet. Spec/malli and friends tell you what to expect at those keys. You can sort of do the same thing in some other programming languages, but generally you're missing one of 1) typechecking 2) namespacing 3) convenience. [0]: spec-ulation keynote from a few years ago does a good job explaining the tradeoffs; https://www.youtube.com/watch?v=oyLBGkS5ICk https://www.youtube.com/watch?v=oyLBGkS5ICk
- kitd 5y agoYeah, he mentions that later on as a drawback
- taeric 5y agoTo be fair, an incoming request is, almost by definition, dynamic. It makes sense to have that as a map, since the main sensible thing to do on receipt is validation/inspection. Granted, you may have a framework do a fair bit of that. Depends how much you want between receipt of the request and code you directly control.
- Zababa 5y agoUsually the approach in a statically-typed language is to transform your dynamic request into something that you know through parsing instead of validation. Here's a great article about this: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va....
- pron 5y ago> We tried VisualVM but since Clojure memory consists mostly of primitives (Strings, Integers etc) it was very hard to understand which data of the application is being accumulated and why. You should try deeper profiling tools like JFR+JMC (http://jdk.java.net/jmc/8/ http://jdk.java.net/jmc/8/) and MAT (https://www.eclipse.org/mat/ https://www.eclipse.org/mat/).
- gavinray 5y agoI was going to suggest this -- inside of VisualVM, you can right-click a process and then press "Start JFR" Then wait a bit, right click it again, and select "Dump JFR" What you get is a Flight Record dump that contains profiling information you can view that's more comprehensive than any language I've ever seen. I used this for the first time the other day and felt like my life has been changed. Specifically, if you want to see where the application is spending it's time and in what callstacks, you can use the CPU profiling and expand the threads -- they contain callstacks with timing There's some screenshots in an issue I filed here showing this if anyone if curious what it looks like: https://github.com/redhat-developer/vscode-java/issues/2049 https://github.com/redhat-developer/vscode-java/issues/2049 Thanks Oracle.
- aliswe 5y ago> ... and the question regarding choosing Clojure as our main programming language rose over and over again If I find myself having to repeat myself justifying a certain decision time and time again, it's an indicator that the decision needs to be revised to be something which is a more intuitive fit for the organization.
- lostcolony 5y agoNot really; it's like stoplights. You're going to be interrupted and therefore notice the red lights, and just sail easily through and thus not notice the green lights. Likewise, you're going to notice the pain points, but need to take a minute to reflect to notice the benefits. Really, if repeating the same justifications convinces people, then the problem isn't the justifications.
- yakshaving_jgt 5y agoAlternatively, you could document the thought process that lead up to the decision and you can point the unenlightened to the documentation instead of having to repeat yourself.
- joelbluminator 5y agoI donno why you're being downvoted, it's a questionable decision and probably the company would have been better off with Python/PHP/Node. Hiring and onboarding are extremely important for a startup. You know what else? Finding answers to common questions on Google/Stackoverflow; I am now working with Ember and can tell you guys you take a 50% productivity hit by using a tool that's obscure on Google. Sure once you become super familiar with a tool that matters less, but that takes time. Much more time. React/Angular may be an inferior tool to Ember but the fact that you can get answers to almost any question is priceless. The community size is super important. The frameworks are super important (is there a Closure equivalent to Rails/Django/Laravel in community size, in battle testedness? I really doubt it). That being said, I salute these brave companies for sticking to these obscure languages. Do we want to live in a world where there's only 3 languages to do everything? Even 10 sounds boring. Hell, even a fantastic tool like Ruby is considered Niche in certain parts of the world. I don't want a world without Ruby so I don't want a world without Closure.
- ivanech 5y agoI started working professionally with Clojure earlier this year and this article rings true. I think the article leaves out a fourth downside to running on the JVM: cryptic stack traces. Clojure will often throw Java errors when you do something wrong in Clojure. It's a bit of a pain to reason about what part of your Clojure code this Java error relates to, especially when just starting out.
- finalfantasia 5y agoTo be fair, this is not unique to Clojure. You need to deal with stack traces no matter what as long as you're using any programming language that targets the JVM (even statically type-checked languages like Scala). There are some great articles [1][2] that discuss various simple techniques helpful for debugging and dealing with stack traces. [1] https://eli.thegreenplace.net/2017/notes-on-debugging-clojure-code/ https://eli.thegreenplace.net/2017/notes-on-debugging-clojur... [2] https://cognitect.com/blog/2017/6/5/repl-debugging-no-stacktrace-required https://cognitect.com/blog/2017/6/5/repl-debugging-no-stackt...
- rockostrich 5y agoI've never really had a problem with stack traces in Scala. Every once in a while you hit a cryptic one that's buried in Java library code, but for the most part they're runtime errors that are due to incompletely tested code or some kind of handled error with a very specific message.
- alaq 5y agoHow did you make the switch? Were you already working for the same company? Did you already know Clojure, from open source, or side projects?
- ivanech 5y agoI work at Ladder [0], and almost everything is done in Clojure/ClojureScript here. I had no previous experience in Clojure – Ladder ramps you if you haven't used it before. My interview was in Python. We're currently hiring senior engineers, no Clojure experience necessary [1]. [0] https://www.ladderlife.com/ https://www.ladderlife.com/ [1] https://boards.greenhouse.io/ladder33/jobs/2436386 https://boards.greenhouse.io/ladder33/jobs/2436386
- lmilcin 5y ago> Pure functions make code design easier: In fact, there’s very little design to be done when your codebase consists mostly of pure functions. Ummm... I am a little bit fearful about your codebase. If you don't see the need for designing your FP system it probably mostly means it is being designed ad hoc rather than explicitly. If you are trying to compare to OOP system done right, you will notice that this includes a lot of work in identifying domain model of your problem, discovering names for various things your application operates on, and so on. Just because you elect to not do all of this doesn't mean the problem vanishes, it most likely is just shifted to some form of technical debt. > Clojure is a dynamic language which has its advantages but not once I stumbled upon a function that received a dictionary argument and I found myself spending a lot of time to find out what keys it holds. Dynamic typing is a tradeoff which you have to be very keenly aware of if you want to design a non-trivial system in a dynamically typed language. It is not a problem with Clojure, it is just a property of all dynamically-typed languages.
- capableweb 5y ago> If you don't see the need for designing your FP system it probably mostly means it is being designed ad hoc rather than explicitly. Why does FP seem to imply that things are designed ad hoc rather than with purpose? I've been working exclusively with FP codebases for the last 5 year, and all designs have been by identifying the domain model and implement it with purpose, with a plan. > includes a lot of work in identifying domain model of your problem FP does not exclude creating a domain model of your problem, discovering names and so on, not sure why you think so? Love to hear the reasoning behind this view you have.
- lmilcin 5y agoI think you misred my comment and think I think exactly the opposite from what I wrote. To reiterate my point: whether OOP or FP you still need to invest time researching, understanding and writing down domain model and designing your application. And if you don't, the problem doesn't go away and instead hides in some form of technical debt.
- scotty79 5y agoI tried to use Clojure but what put me of was that simple mistakes like missing argument or wrongly closed bracket didn't alert me until I tried running the program and then gave me just some java stack spat out by jvm running Clojure compiler on my program. It didn't feel like a first class experience.
- capableweb 5y ago> didn't alert me until I tried running the program That's because that's not how Clojure developers normally work. You don't do changes and then "run the program". You start your REPL and send expressions from your editor to the REPL after you've made a change you're not sure about. So you'd discover the missing argument when you call the function, directly after writing it.
- scotty79 5y agoInteresting. How exactly that looks? Do you have files opened in your editor, change them then go into previously opened repl, and just call the functions and the new version of those function runs?
- uDontKnowMe 5y agoThat's right. You typically would have your text editor/ide open, and the process you're developing would expose a repl port which your editor can connect to. As you edit the source code, that will automatically update the code running in the process you're debugging. See this demo of developing a ClojureScript React Native mobile app published yesterday: https://youtu.be/3HxVMGaiZbc?t=1724 https://youtu.be/3HxVMGaiZbc?t=1724
- deleted 5y ago[deleted]
- finalfantasia 5y agoThanks to the dynamic nature of Clojure programs, experienced Clojure developers use the REPL-driven development workflow as demonstrated in this video [1]. [1] https://youtu.be/gIoadGfm5T8 https://youtu.be/gIoadGfm5T8
- user3939382 5y agoWalmart Labs was a step in this direction.. but we need some big companies to standardize around Clojure to jumpstart the ecosystem of knowledge, libraries, talent, etc. I’ve spoken to engineering hiring managers at fairly big companies and they’re not willing to shift to a niche language based only on technical merits but without a strong ecosystem. If we don’t get some big companies to take on this roll the language is going nowhere. I’m saying this because I’m a huge fan of Clojure (as a syntax and language, not crazy about the runtime characteristics) and I hope I get the opportunity to use it.
- iLemming 5y ago> If we don’t get some big companies to take on this roll - Cisco - has built their entire integrated security platform on Clojure - Walmart Labs and Sam's club - have some big projects in Clojure - Apple - something related to the payment system - Netflix and Amazon, afaik they use Clojure as well even NASA uses Clojure. I think the language "is going somewhere"...
- user3939382 5y agorole* lol we made the same mistake. There is some adoption to be sure. But look at Google Trends for clojure.
- iLemming 5y agoWhat does Google Trends have to do with a programming language? PHP is trending, and Clojure is not, perhaps because Clojure gives you a lot fewer reasons to google stuff up? I myself rarely use Google to find a solution to a problem, and certainly almost never have to google shit like: "how to open a file in Clojure"...
- cgopalan 5y agoAnother good report about what Clojure does well is this article by metabase: https://medium.com/@metabase/why-we-picked-clojure-448bf759dc83 https://medium.com/@metabase/why-we-picked-clojure-448bf759d... I have had the pleasure of contributing to their code since we used their product at a previous company I worked at, and I must say I am sold on Clojure. Definitely a great language to have in your toolbox.
- altrunox 5y agoGreat article, love Clojure, unfortunately couldn't find any work with it when I tried, I managed to flop in the only interview I got :( Still, I miss it sometimes when I'm writing C#.
- didibus 5y agoBeing able to keep track of what data was where is the initial bump I had as well when learning Clojure. Unlike the author, I personally got used to it, and generally don't struggle with it anymore, but part of that is learning good habits on your code base where you make judicious use of names, doc-string, destructuring and have a well defined data model using records or Spec or Schema, etc. The other one is just getting good at the REPL and inspecting the implementation for functions to quickly see what keys and all they make use of. Something the article didn't really cover either is that it's not really the lack of static type checking that's the real culprit, its the data-oriented style of programming that is. If you modeled your data with generic data-structures even in Haskell, Java, C# or any other statically typed language, you'd have the same issue. If Clojure used abstract data-types (ADTs) like is often the case in statically typed languages, things would already be simpler. (defrecord Name [first middle last]) (defn greet [{:keys [first middle last] :as name}] (assert (instance? Name name)) (println "Hello" first middle last)) (greet (->Name "John" "Bobby" "Doe")) This is how other languages work, all "entities" are created as ADTs, it has pros/cons off course, which is why Clojure tend to favour the data-oriented approach where you'd just do: (defn greet [{:keys [first middle last] :as name}] (println "Hello" first middle last)) But as you see, this makes it harder to know what a Name is and what's possibly available on it.