12 ms·
I've switched my own back end codebase to Clojure. It's just a joy to work with. Building REST with Liberator is a revelation. It's so easy to reason about what
by beat 13y ago
I've switched my own back end codebase to Clojure. It's just a joy to work with. Building REST with Liberator is a revelation. It's so easy to reason about what the code is doing, and immutability leads to far fewer bugs. Not missing objects, not at all.
The stock concern for businesses building with Clojure is the lack of talent and industry acceptance. I have a different angle. I figure that when I'm ready to start hiring other developers, I'll only be hiring developers sophisticated enough to see the benefits of using Clojure over Java/Ruby/Python/etc - and then I'll have the draw of letting them work in the best tools, not making them compromise so it's easier for management to hire less serious programmers.
- platz 13y agoIs it still so easy to maintain Clojure code given it's lack of types? (I know about Typed Clojure; it doesn't seem to have won the hearts of all Clojurians) (Or, do you just test the bejezus out of the thing?)
- lkrubner 13y agoI add :pre and :post assertions to most of my functions, so I know the type of everything coming in, and also what is returned. An example of a function that saves a document to MongoDB: (defn persist-this-item [item] {:pre [ (map? item) (= (type (:item-name item)) java.lang.String) (= (type (:item-type item)) java.lang.String) (if (:created-at item) (= (type (:created-at item)) org.joda.time.DateTime) true) ]} (let [item (if (nil? (:created-at item)) (assoc item :created-at (tyme/current-time-as-datetime)) item) item (assoc item :updated-at (tyme/current-time-as-datetime)) item (assoc item :_id (ObjectId.))] (mc/insert "tma" item))) So this function needs to be given some kind of map, and it needs to have 2 keys, :item-name and :item-type, and both of these need to be strings, and if :created-at is already set, then it needs to be a Joda DateTime. It's not accurate to say that Clojure has no types. It just gives you some flexibility about how strict you want to be. I find that Clojure strikes a perfect balance: its flexibility with types allows me to easily integrate a lot of 3rd party tools without much work, but when I need to I can be as strict as a I like with types. Edit to add: wow, the code is ugly on Hacker News. Funny that this site provides no tools for posting code snippets, given the subject.
- deleted 13y ago[deleted]
- thinkpad20 13y agoMaybe I'm misunderstanding, but it looks like every time these functions get called, they're type checking their inputs and outputs? That seems like a waste of resources. Why not just write unit tests that ensure that they will always be called with the types you expect (or fail at an earlier step if not)? Identify the places where there might be ambiguity in something's type (e.g. when a JSON blob arrives, it might map a key to a list instead of a dictionary, or if a function might return multiple types, etc), and use unit testing to ensure that the type you expect will emerge from those places, or else a failure will occur.
- Tobani 13y agoI believe :pre and :post conditions only get run when assertions are enabled. There are other languages that have this feature (Eiffel touts it pretty heavily), but I haven't seen the AHA! example where this is the long lost feature I've been missing. But that said, :pre/:post w/ unit tests seems pretty powerful. You can assign the invariants to the functions themselves and have a better / more robust set of assertions in your unit tests.
- thinkpad20 13y agoIf it only will get run in some sort of testing mode, that's fine I suppose. Although the other drawback to this sort of thing is that it seems to really decrease readability. Most of the time when you're reading code, you just want to see what's actually happening, and having a big messy set of type assertions would be noise 90% of the time (especially because unlike type signatures in Haskell, say, there's no syntactical difference that would allow syntax highlighters to help you visually see what's actual code and what's type assertions).
- Tobani 13y agoSo I wouldn't confuse invariants w/ type assertions. Clojure does have separate type assert/checking in core.typed. Yes one thing you might use :pre/:post for is type checking, but it can do more than just that.
- tsm 13y agoTests, core.typed, prismatic/schema, and/or pre- and post-conditions. It's still saner than, say, Ruby.
- Argorak 13y agoFWIW: "What does this take and what does it return?" is rarely a question that comes up in Ruby for me. In most codebases, thats a pretty easy question. For others, Clojure wouldn't help at knowing whats going on ;).
- tel 13y agoThat was what drove me away from Clojure in the end. It's fun to write, but a challenge to write in such a way that you can read it later without jumping all over the place.
- cryptolect 13y agoHow big/complex were your functions? Have you seen the style guide? It's easy to write really powerful, nested functions in Clojure, which are damn hard to read the next morning. Taking advantage of thread-first and thread-last macros can really help here. I've finally started to use them heavily. For instance, you could do the following to read a file, parse it from json, and retrieve somekey: (:somekey (cheshire/parse-string (slurp "/tmp/somefile.json") true) Or (-> "/tmp/somefile.json" (slurp) (cheshire/parse-string true) (get :somekey)) In other words, there's much nicer ways to write maintainable, readable code that you can still understand in the morning. Your "jumping around" comment also makes me wonder how you were laying out your functions? My advice is to go through the libraries of the popular Clojure libraries, you will discover a lot of good practices. It's taken me a few months of Clojure to start writing cleaner, readable code.
- airfoil 13y agoI had the exact opposite experience. I found that I was laser focused on a particular chunk of code when working in Clojure. Functions were logically grouped together. I never found myself jumping around.
- beat 13y agoFrankly, Clojure doesn't need the intense levels of testing that OO languages require, due to immutability. There's a great deal less forking of possible states to examine. I have a friend who is a strong typing devotee who has worked heavily in Clojure for a couple of years, and he curses the lack of strong typing - but he's comparing to the likes of Haskell and OCaml. As a dynamic typing enthusiast myself, I'm comparing to Ruby, and Clojure is definitely superior in terms of how much it needs to be tested. What drew me to Clojure over Ruby, though, was actually a matter of data modeling. I'm working with a graph database (Neo4J), and I found that good graph technique is terrible OO technique. I was violating the Law of Demeter constantly, reaching through objects to get to other objects, passing internal state around with the express intent of mutation, etc. Switching to a functional paradigm lined up perfectly with good graph design. No more smells! No more pain! And again, a lot less testing, because I'm no longer fighting against the nature of my data.
- fiatmoney 13y agoOne of the really great things about having rich built-in data structures and a more-or-less purely functional data model is that it's really easy to express transformations and be confident that they work. In Java it's incredibly verbose to express & manipulate something like "a list of pairs of sets of integers" because if that was part of your core data model, you'd want to represent it in a class, and then you have all sorts of problems about what callers expect vs. what they get (e.g., do I get a reference to the original, or a copy? Can I safely mutate this?). But if you actually have facilities to pass around and manipulate those kinds of data structures in a way with no side effects, it's trivial to verify (first on the REPL, then in your unit tests) that whatever transformation you're writing applies properly down the entire nested chain, and it will always work for anything of that pattern. And it is much quicker to write those kinds of transformations iteratively in a dynamic language than trying to represent it on a type level a la Scala.
- beat 13y agoThat's a good point. A lot of what I built classes and objects for in other languages can really be expressed as simple maps, hashes, lists, and vectors, and more complex types built from them. Clojure's elevation of maps/hashes/vectors to first-class types puts it above Lisp, imho, and its functions to manipulate said data structures are better even than languages like Ruby and Python. I can't imagine having to go back to Java collection classes again. Ugh.
- lispm 13y agoYou are saying hashes, vectors, etc. are not first-class types in Lisp? Strange...
- auvrw 13y agobut just in case someone considering clojure is worried about missing something like haskell's typeclasses (or whatever they're called in scala), it's worth mentioning that clojure does have some notions of "protocols" and "datatypes" that seem pretty similar.
- 13y ago
- _halgari 13y agoMany times the clojure code is so much simpler, a) even basic testing will catch most of the bugs and b) there are less bugs since the code is so dense. In many ways it's just simpler to understand what 5 lines of Clojure code are doing, than 50 lines of C# code. On top of all that, sane defaults like immutable data, lack of mutable locals, and sane concurrency primitives means that a whole class of bugs just don't exist in normal Clojure code. All this sums up to a codebase that really doesn't need static typing. I write Clojure code for a living, and have worked on some really large Clojure codebases, and yet I've never felt the need for a type system. It's a fallacy that large systems have to be so complex that only a type system will save you. Often the better route is to simplify and modularize the system.
- cryptolect 13y agoI use prismatic/schema which can be used to define schemas and either validate on demand, or always-validate function inputs. Very useful and easy to add.
- doktrin 13y ago> I'll have the draw of letting them work in the best tools, not making them compromise so it's easier for management to hire less serious programmers. Why are developers who have not worked with Clojure "less serious" than those who have?
- bretthoerner 13y agoIt's not just about Clojure, but Clojure is one good signal. http://paulgraham.com/avg.html http://paulgraham.com/avg.html
- mattgreenrocks 13y ago"Less serious" programmers would rather argue that coupling doesn't exist on HN than sit down and study why people try to avoid it.
- michaelochurch 13y agoWhy are developers who have not worked with Clojure "less serious" than those who have? In a typical Java shop or enterprise code factory, after 2-5 years you reach a plateau and the only way up is to become an "architect" or move into management. So that's what the better people in the mainstream Java world want to do, and ultimately the tenor is focused on "enterprise architecture" and Spring/Hibernate add-ons instead of making beautiful, powerful, or maintainable software and solving hard problems. The Clojure community (I'm at Clojure/West as we speak) is full of people who (a) are really good, like top 1%, and (b) have at least the intention to be career-long programmers (in some capacity). That's something you don't see in the Java shops. Yes, there are good programmers in them, but they usually are planning a move into management because the way most companies do software is limiting and puts you at an artificial ceiling after no more than 5 years. Is it a gross simplification to make this about languages? Perhaps, but the empowerment that languages like Clojure give to individual contributors can be a game changer. It's reminiscent of what startups used to be, before the VC-funded ecosystem became MBA Culture's west coast colony.
- laureny 13y ago> The Clojure community (I'm at Clojure/West as we speak) is full of people who (a) are really good, like top 1% Says someone part of the Clojure community. How does it feel to be one of these top 1% developers? This kind of attitude is why the Lisp community has had a terrible reputation for five decades.
- RyanZAG 13y ago> I figure that when I'm ready to start hiring other developers, I'll only be hiring developers sophisticated enough Spoken like someone who has never had to actually hire people! It's harder than you think.
- mtrimpe 13y agoReally? I've found that it's easy as pie to find extremely highly skilled developers from my local Clojure meetup and get them interested at well below market salaries. I must admit that I was looking for a bootstrapping co-founder instead and even 50% market rate didn't make sense in that context so I don't know if they would've followed through, but it sure as hell didn't feel like a tough sell.
- jshen 13y agoso you were trying to hire 1 person? And from that you conclude that it's easy to find many good people at below market rates?
- beat 13y agoFor a startup, you don't need "many". You need a handful. If I'm trying to hire three programmers and they are make-or-break for the success of my company, I'd much rather be picky. I'll leave the hordes of generic Java coders to the people who have to hire a hundred at once.
- tieTYT 13y agoI made two hobby projects in Clojure and the biggest downside to it was the lack of typing (yes, I know of core.typed). Specifically, I found it very intimidating to refactor my Clojure code compared to Java. This isn't apples to apples because I write a lot more tests in my Java code than I did for my Clojure code. But, one of the reasons I went so light on the tests is because I've watched more than one Rich Hickey talk where he seems to make fun of TDD. I assumed he knew something I didn't and that automated tests would be far less important in Clojure than Java. Could you explain why the difficulty I faced in refactoring Clojure code is not issue for you?
- calibraxis 13y agoNot sure what Rich Hickey thinks, but my impression is he believes that example-based testing (http://www.infoq.com/presentations/Simulation-Testing http://www.infoq.com/presentations/Simulation-Testing) is superior to other kinds of testing. Also, he promotes design rather than only coding (typing); perhaps when you did less TDD, you didn't invest that time to do more design? (Maybe TDD was a time to do design?) Anyway, you don't have to believe what he believes; for example Brian Marick's Midje. (https://github.com/marick/Midje https://github.com/marick/Midje) Whatever makes you the best Clojure programmer is right for you.
- mtrimpe 13y agoDid you use Prismatic's Schema [1] at the time? It seems like that was developed for your exact problem/use-case. By the way; I don't think Rich makes fun of TDD, but of how little of TDD is left when you're working with purely functional, side-effect free, code: passing functions data and verifying that you get back the right thing. In practice every Clojure coder will tell you debugging is still a pain but that it's not a problem as long as you check each small, pure, function for correctness ... which is pretty close to, if not the same as, TDD. [1] https://github.com/Prismatic/schema https://github.com/Prismatic/schema
- sitkack 13y agoTypes don't prevent bugs (just classes of them) and small pure functions can still be composed into incorrect larger programs. ---- Edit to subdue harshness... I wrote a huge ETL tool in Python using all pure functions, list comprehensions, only named_tuple, no classes, no mutation, lots and lots of garbage, but it worked and worked well. I took a two pronged approach to testing 1. Proved each pure function in separate file, this was for me, this stuff never got run again. It was like a nursery for pure transformations 2. Wrote high level integration tests, 1 or 2 per module (about 20 total) 3. If I had a nasty bug, I would set a breakpoint in the debugger (pdb) and duplicate all of the state around me as best I could and put it into a functional test that WOULD get rerun as part of the automatic testing cycle. I would love a tool that could extract program state and put it into a test for me. 4. The majority of testing was to run the ETL tool on subsets of the input and validate the output, I had a handful of these of increasing complexity. The output validation was automatic. Testing a 4-10 line functions is waste once it is constructed. Higher level, whole module tests should cover an individual breakage of a smaller pure function. Tests are baggage (sometimes useful), just as static types are baggage (also sometimes useful). We need baggage, gotta wear clothes, read and eat.
- Jd 13y agoFrom a startup's perspective (my own), it's not so easy to find Clojure folks. Ever since I founded it I have less time to go to meetups (esp. specialized FP meetups) to recruit people, and most people that I interact with when I mention Clojure just say "What's that?" That's to say, so far I regard it as somewhat of a net loss that our backend is in Clojure.
- dominotw 13y ago> figure that when I'm ready to start hiring other developers, I'll only be hiring developers sophisticated enough So basically hire people that think exactly like you. That always works well.
- leishulang 13y agoas a developer, it's harder to find a clojure shop close to my location. More clojure I use, less java/c#/php/python/ruby stuff I am willing to do. This makes interviewing harder for me too. I might just jump back onto non-hipster languages to make my life easier.