17 ms·
Fourteen Months with Clojure
- jtmarmon 10y agoI loved the either-monad solution. The problem is something I've experienced many times in my clojure adventures, usually resulting in either dirtying the function in question with its own, case specific logging or creating the if-let monstrosity you demonstrated. As a side note: there's something very comforting when you immediately empathize so strongly with a programming problem someone is writing about
- lackbeard 10y agoThat part really resonated with me as well. Well the whole article did... it really matches my professional experience with Clojure (except I've never looked at transducers.) I still don't feel great about error handling in Clojure in general. I can't find something that feels both practical and idiomatic. I want to use monads but it always feels like huge impedance mismatch with the rest of the language (most of the time due to nil being semantically overloaded.)
- sdegutis 10y agoFor the nested if-let mess, I'd probably do something like this: (let-every [x (foo) err "foo failed" y (bar x) err (format "bar %s failed" x) z (goo x y) err (format "goo %s %s failed" x y)] (qux x y z) (handle-error err)) Where `let-every` is a macro that works like let, but stops short on the first nil/false variable, runs only the next symbol binding expression, and then runs the else-clause. There'd be nothing special about the "err" symbol on each line. It's just the next symbol binding, but on the same line as a convenience, and this means it can reference any previously valid symbol bindings. Here's a quick & dirty implementation of that macro. I don't have a Clojure interpreter installed, so I don't know if it works. (defmacro let-every [bindings if-body else-body] (let [pairs (partition 2 bindings) quad-pairs (partition 2 pairs)] (loop [quad-pairs quad-pairs] (if quad-pairs (let [[quad-pair] quad-pairs [try-pair err-pair] quad-pair [try-sym try-expr] try-pair [err-sym err-expr] err-pair] `(if-let [~try-sym ~try-expr] (recur (next quad-pairs)) `(let [~err-sym ~err-expr] ~else-body))) if-body)))) Given the above example, it should expand to this: (if-let [x (foo)] (if-let [y (bar x)] (if-let [z (goo x y)] (qux x y z) (let [err (format "goo %s %s failed" x y)] (handle-error err))) (let [err (format "bar %s failed" x)] (handle-error err))) (let [err "foo failed"] (handle-error err)))
- kazinator 10y agoGiven the existence of exception handling, I would rather apply the pattern: ;; Plain old ANSI Lisp (let* ((x (or (foo) (error "...")))) (y (or (bar x) (error "bar ...")) ...) body) I mean, if, in the end, we are going to "error out", rather than just forward-propagate `nil`. If we are going to just propagate `nil`, then if we have an `iflet` that tests the last variable, we can do: (iflet ((x (expr)) (y (if x (bar x)) (z (if y (foo x y))) ;; z is tested by iflet ...) it's just a bit verbose. That can be condensed with a simpler macro that doesn't have the err stuff.
- sdegutis 10y agoYour solution doesn't short-circuit when any of the calls fail. It relies on (error) throwing some kind of exception to interrupt control flow and prevent the following lines from executing.
- kazinator 10y agoYes; I'm using ANSI CL syntax/semantics. error denotes cl:error which can in fact be relied upon to throw. That's what I mean by "given the existence of exception handling ...". Substitute your favorite dialect's error thrower.
- egamble 10y agoI wrote something like that several years ago: https://github.com/egamble/let-else https://github.com/egamble/let-else
- sdegutis 10y agoReally cool! May have to borrow this some day.
- SomeHacker44 10y agoI really like `:delay`. It will allow efficiencies in my already Haskell-esque "define all possible values used in a single big let" style I program in. My code all handles nil safely and returns nil when appropriate but it would be even better to avoid evaluating things unless used in the body of the else (or a later binding).
- jcadam 10y agoI've been using the cats library myself: https://funcool.github.io/cats/latest/ https://funcool.github.io/cats/latest/ and find it really useful. I mostly use the Maybe and Either monads (and occasionally the exception monad if I'm calling some function in a third-party library that might throw an exception).
- tel 10y agoHaving used the cats library in anger... I think for Clojure it'd probably be better to make a couple specific monads to handle, e.g., either-ing. Talking about monads abstractly really requires type support, I've come to believe. It can be done otherwise, but it gets really complicated.
- losvedir 10y agoOof, I don't know. I love Lisps, generally, having gotten my start with Scheme back in the day. But that monadic thing just rubs me the wrong way. I don't program Clojure, though, so maybe it's just lack of experience. If they had gone with Rust's Ok/Error rather than Haskell's Left/Right, maybe it would be better to me. (Yes, yes, I know, the monad is more general than that, but if it's used 99% of the time for handling errors, maybe optimize for that use case.) I program in Elixir now, which I'm totally loving. It's also a "no early return" and "totally immutable" language, so I wonder if any "cross pollination" between the languages is possible. This is how I would code the example given in Elixir: with {:ok, x} <- foo(), y when not is_nil(y) <- bar(x), {:ok, z} <- goo(x, y) do qux(x, y, z) IO.puts "it worked" true else {:error, :not_fooable} -> IO.puts("foo failed"); false {:error, :no_bar} -> IO.puts("bar failed"); false {:error, :no_goo} -> IO.puts("goo failed"); false nil -> IO.puts("something (bar?) returned nil"); false _ -> IO.puts("some other error"); false end Elixir's cool "with" / "else" macro is used for chains of calls where the intermediate steps can fail. In fact, it was introduced not too long ago, just to deal with nesting code issues like those raised here! It relies on elixir/erlang's pattern matching to specify the happy path, extracting out the success responses (e.g. {:ok, x} will match a response of {:ok, 5}, and x will hold the value 5). If any of the happy matches fail, it falls through to the `else` and you can catch any particular failures you want there. It's a really great system. Someone should try to add it to Clojure! Not sure how well the "pattern matching" aspect of it can work, though.
- abecedarius 10y agoIt looks like this in my personal Lisp dialect, with no new macros: (match (foo) (#no (log "foo failed") #no) (x (match (bar x) (#no (log "bar failed") #no) (y (match (goo x y) (#no (log "goo failed") #no) (z (qux x y z) (log "it worked") #yes)))))) I imagine any language with a match macro, like Racket, could look similar. Using monads or exceptions ought to look nicer, but I'd kind of rather not pull out the big guns.
- deleted 10y ago[deleted]
- _vya7 10y agoAfter spending 50 months with Clojure, I can safely say it's my favorite server-side programming language (with Datomic as my favorite database), and the tooling (CIDER + Paredit + Emacs) is really downright amazing in terms of productivity.
- jtmarmon 10y agoAlthough the tooling is great, I found it super frustrating that you basically have to use the entire suite of prescribed tools to feel productive. Basically everything except emacs sucks with clojure, especially if you're a vim user. I found that pretty frustrating. Vim fireplace sucks. Then if you want to use a gui editor with a vim plugin you have to abandon the vim world and use editor-esque paredit plugins (since no one is developing vim-style paredit for vim-editor-plugins) gasps for air. Then finally there's emacs which I really don't want to get started with.
- hacker_9 10y agoI switched to IntelliJ + Cursive, and haven't looked back since.
- SomeHacker44 10y agoYes! 1000 times, yes!
- sdegutis 10y agoYeah it's a very real form of lock-in, nothing else really compares to Emacs + Paredit + Cider for productivity. And Emacs isn't great. But when I did Clojure full time for 5 years, I just sucked it up and learned Emacs and got really good at using it. Customizing my init file little bits at a time probably added up to a month's worth of lost productivity. But out of 50 months, 1 month lost to environment setup isn't bad. That's like 2% of the whole time. Or about 48 minutes per week. And the learning curve for me (as a long-time vim user) wasn't as hard as I thought it'd be, especially since knowing bash shortcuts really helped prepare me for the Emacs way. In fact I even took some Emacs knowledge back to the shell, such as how Ctrl-underscore is "undo the last edit to the current command".
- baumandm 10y agoThis was a great read. My favorite part was the section about rarely used language features, as I have had a similar experience. It makes you wonder if you're inexperienced or doing something wrong, when in fact those features may just not be needed most of the time.
- jtmarmon 10y agoMost of the macros you'll need are already part of clojure core, thankfully
- mpweiher 10y agoHaving talked to a bunch of very experienced LISPers recently (including Richard Gabriel) about this very topic, it appears to be the case that most people go wild with macros for a little while, but this passes and then you write fairly straightforward code.
- lispm 10y agoThere is no single answer for that. Some code from experienced Lisp developers uses a lot of macros. Others don't like that style. It also depends on what you are writing.
- cronjobber 10y agoI've written CL macros. I've noticed that for me, the probability of long-term use seems proportional to the size of a macro's implementation. The more work a macro does, the less likely I'm to revert to "straightforward code".
- jwr 10y agoWell, those feature are needed, but good people already made use of them. The "go" macro from core.async is a good example.
- cmollis 10y agocan be said about C++ (or any language). But he (the author) said it much better.
- jonaf 10y agoThe author briefly touches on schemas. Anyone have experience or recommendations working with clojure.spec? If I were using Clojure with a schema less database, would I stand to gain anything significant?
- jcadam 10y agoI've been using plumatic schema: https://github.com/plumatic/schema https://github.com/plumatic/schema in conjunction with compojure-api. It provides a nice way to validate data coming into my REST endpoints. I'm currently using Postgres, but I'd probably still make use of schema if I were going NoSQL.
- rcarmo 10y agoI sorely miss writing Clojure - it's the one thing I had to give up on my current role (largely due to lack of time, but also because I mostly write stuff in Python as a sort of lingua franca), and I find most of the idioms slipping away though disuse. This was an amazing read in the sense that a) it brought back all the reasons why I began using it in the first place. b) it had some great lines - both the "when the going gets tough the tough use maps" title and this lovely tidbit: "...maybe Scala has answers to all of these problems now, as I haven’t had the pleasure of using it in several versions. Do not @ me to talk about this."
- klibertp 10y agoI only worked with Clojure professionally for a couple of months, but the experience was less than stellar. Most of it probably had to do with the codebase being written by people without any experience, but a couple of problems could be traced back to the language itself. I'm preparing to write a lengthy post comparing Clojure and Racket, the other Lisp I have been using for personal projects for a couple of years now. My opinion is that Racket is a better language overall, although it may be less practical in terms of writing production code and it certainly lacks some of the nice features Clojure brings. On the other hand, many of these nice features are available as libraries (not only in Racket - in most Lisps, including Elisp and Common Lisp). For the last two years, I used StumpWM as my window manager and so I learned some Common Lisp. Again, as a language, I think Common Lisp is still better than Clojure, although it's definitely stranger. I use Emacs as my default "computing environment", so I naturally learned Elisp, too. This is the only Lisp I used which I'd consider inferior to Clojure, but even then it works quite well for its use-case. All in all, from the perspective of Lisp family of languages, Clojure doesn't seem to be exceptional in any aspect. It does have nice features, and I bet it feels much better compared to Java, but it also has some downsides to it which are irritating when coming from Racket or Common Lisp.
- bsaul 10y agoFrom someone that has never touched clojure ( or any lisp) i would say that the python equivalent using either/right libraries made me feel like running away as far as i could. Could anyone provide me with an example of a code that would actually read nicer in clojure than in python ? Make it as arbitrary as you'd like, i'm honestly trying to understand . i've read enough about lisp bluring the line between data and code, so i'm starting to get an intuition of its benefit. I was just hoping to finaly see a real world example, and i'm a bit disappointed.
- barkingcat 10y agoThere are a wealth of information about lisp variants, clojure, and functional programming. If you did some simple research you would not be so disappointed. Lisp has been around decades, surely you can find some "real world" examples that you can learn from. Personally, I started reading the clojure documentation and going through 4clojure https://www.4clojure.com/ https://www.4clojure.com/ , a kind of simple programming introduction problems to clojure People have solved all the questions, and you can find them online really really easily https://gist.github.com/SegFaultAX/3607101 https://gist.github.com/SegFaultAX/3607101 for example. Just do some research, and come back once you have done a basic amount of work, and can no longer say "I've never touched clojure". Instead, come back and say "I did some research, I worked through some 4clojure questions, and I still don't understand..." then I think you'll get a more productive discussion.
- unit91 10y agoNicer to read is pretty subjective, but I'll tell you why I've switched 2 projects from Python and Ruby: speed. Our systems just had too much to crunch through, and the global interpreter lock pretty much killed us. We switched over to Clojure in 2009 and never looked back. In the early days, we had to wrap a lot of Java, but it's been several years since that was necessary. Currently, Clojure + Kafka + Apache Storm is the scaleability trifecta. One final comment, which is often tragically overlooked, in my opinion: ease of deployment. Package management and deployment often sucks in other languages (especially in Ruby). By contrast, Leiningen is a SWEET dependency management tool, and the built-on-Java approach leaves you with a jar that you scp and you're done.
- SCdF 10y agoI only ever monkeyed around with Clojure, but on their first example: is it weird I'm OK with both? The only thing that surprises me about both of those examples is that :state is a collection of things not just one thing, but I imagine if you're more familiar with their structures that would be less of an issue.
- mcfunley 10y ago(author) I just grabbed an example at random there. It's not really an excessive example of nesting, but, at the time I didn't grok threading. I think the data being manipulated is a cloudformation response or something, so, the structure isn't something I'm in control of. I actually changed it from `(:status (:status ...` in the original, which is even dumber naming, so that it wouldn't look like a typo. Real life programming is thoroughly unglamorous I guess.
- jfaucett 10y agoClojure is interesting in a lot of ways. I've toyed with it a lot, but what actually keeps me from using it much is how tightly coupled it is with the JVM. I know this is its big selling point for a lot of people but for me personally I don't enjoy having to know Java's APIs and ecosystems to get things done. Is anyone else out there like me who wishes there was a standalone clojure implementation that wasn't a hosted language? Add a modern package manager on top of that like cargo or mix and I would love to write new projects in it, because clojure itself is a very pleasant experience.
- slowmovintarget 10y agoRegarding coupling to the JVM: https://funcool.github.io/clojurescript-unraveled/ https://funcool.github.io/clojurescript-unraveled/ Regarding being a hosted language... that was it's intent. Use one of the best run-times available, with a better language. Granted once you design the language to be hosted, it can find other hosts as we see in ClojureScript.
- klibertp 10y agoWith ClojureScript you end up relying on Google Closure Compiler as much as you rely on Java with plain Clojure. It's true that being a hosted language is one of the goals of Clojure, but it's also true that being a hosted language is a non-feature (at best) for some people, including GP and me :)
- lispm 10y agoHow is Clojure 'designed' to be hosted? What is the design there, other than just leaving out functionality and delegating it to the host environment? Other languages which weren't 'designed to be hosted' can't be hosted? Hmm, I was under the impression that the JVM hosts a lot of standard languages, for example Scheme dialects and Common Lisp.
- jdminhbg 10y agoIt makes access to the underlying host a first-class language feature, so interop with the JVM or JavaScript is super-simple.
- jwr 10y agoI write lots of Clojure and ClojureScript everyday, and the article really resonated with me. Especially the parts about not using anything too complex or fancy, and sticking to simple tools. My code also has very few macros, and relies on lots of (complex) maps, using clojure.spec to keep them in check. I think you get even more benefits from Clojure if you write apps that run both server-side and client-side (in the browser, using ClojureScript). Much thanks to the author for nicely showing a good use case for the Either monad. In general, I think there are lots of great concepts in category theory, but well hidden behind horrendous naming (bind/return, anyone?) and lack of good practical examples (well let's fmap inc over a vector here). And since it seems everybody pitches in something about some language being inferior or superior to Clojure, I'll contribute something, too: I would not have been able to write PartsBox.io (https://partsbox.io/ https://partsbox.io/) without Clojure and especially ClojureScript. It boils down to practical reasons: code size, abstractions that I can build on, avoiding accidental complexity. I don't like participating in discussions that compare programming languages these days. I feel these are often very shallow. As an example, I used to program a lot in Common Lisp, and I think it can't meaningfully be compared to Clojure/ClojureScript. You don't see that until you've written several large multithreaded applications with zero deadlocks (thanks to STM), after you've used core.async to simplify complex and bug-prone code, after you've used transducer pipelines to parallelize a large stream computation completely avoiding the horrors of Hadoop, or after you've sent your data structures over a websocket to code that is actually the same code that runs on the server (cljc, files that compile to both Clojure and ClojureScript). Seriously — how can I discuss the finer points of syntax (oh, the parentheses!) when I am able to ship React-based apps that do isomorphic rendering (the server pre-renders a page, and JavaScript plugs into it later), all in Clojure+ClojureScript, without even touching node.js? You start to appreciate those things only when you write large applications and your time is limited. Going back to my Common Lisp background, I also wrote web apps in CL, and there is no way I would even think about going back. There are things I do not like about Clojure, but so far I haven't found anything even remotely comparable in terms of real-world productivity. So I'm sticking with what works really well, at least until something better comes along.
- raspasov 10y agoThis should be the top comment in this thread. A lot of garbage comments overall.
- mark_l_watson 10y agoI have over one year of clojure experience on customer projects, spread out over the last six years. I like the language a lot, but except for my cooking website (cookingspace.com) and the server side code for an Evernote clone prototype, I don't use Clojure on my own projects where I tend to use Haskell for the fun of using Haskell and then Ruby and Java are my go-to languages to get stuff done quickly. I agree with most of the author's opinions about the use of Clojure except that personally I dislike using multimethods.
- thom 10y agoClojure's been my go-to language since around 2009, and I use it full-time today. A few points from the article: The horrible if-let code the author shows is exactly the kind of thing people use macros for. I'm surprised nothing exists in clojure.core for this, but every project I've ever worked on has something like if-lets, which short-circuits on the first falsy assignment. Everyone thinks they don't need macros and then finds stuff they hate in the language which is easily fixed by macros. This is probably true of the non-mainstream paradigms in all languages. It takes some willpower and humility to learn all Haskell's lens arrow operators instead of just laughing at them. clojure.spec is nice, and I use it in my current project, but it has performance issues (it is after all basically alpha code) and I'll be honest that I don't _really_ understand what the workflow is supposed to be. If you deal with a lot of side-effectful code you might be using conforms and asserts to keep things sane, but I'm not really benefiting from the generative testing bits. Thinking in terms of generators for code that _is_ pure just feels like another job that I've for some reason forced on myself. Anyway, at the very least, it does make the world of procedural code operating on maps a little safer and saner and you should probably try it. My complaints about Clojure are nothing to do with the language, which I think is absolutely fine. My main complaint is that Clojure people are smart, and fundamentally believe in small libraries over frameworks, and this means that the Clojure landscape is scattered with abandoned overly-specific libraries, and abandoned and unloved big frameworks. If I could, I would pay someone full time to maintain Incanter, but it's now basically dead, and that means Clojure has no useful numerical computing environment. I am lucky that I can get by with what already exists in Incanter and what's in clj-ml for my analytics work, but nobody is going to quit Python or R for Clojure at this point unless they're smart enough and willing to write all this for themselves. I happen to do some web stuff and I think ClojureScript is about as good a language as you're ever going to get on the browser. But the other consequence of a community where most people are smarter than you is that the best front-end frameworks are utterly inscrutable. Om has wonderful functionality that I think I would like in my app, but I am too dumb to understand all the moving parts. Reagent is very elegant but is tougher to scale to complex web apps. I still believe that Clojure is a beautiful and productive language. Year after year I see other languages pile on new syntax while Clojure just remains brackets and functions. For the parts of my code that are pure and functional it's wonderful, for the parts that are ugly and pragmatic I have the full range of the JVM landscape available to me. I get to stay in Emacs, and Cider me custom elisp does everything I need for an IDE. But if anyone asked me to recommend a language to spend their lives in I would probably just say Python and if you do web stuff JavaScript. Bigger community, better maintained open source projects, and just a clearer roadmap to mastery.
- bsder 10y ago> Why not? If a language's goal is to be practical (as is Clojure's), then it should take practical concerns into consideration. This is something that the Java community definitely gets right. AHAHAHAHAHAHAHAHAHAHAHAHAHA! Java getting practical considerations right. Oh, I so needed a good laugh. Let's start from the basic: No unsigned type. Bit/byte manipulation code written in Java is garbage when written by experienced programmers. When written by inexperienced programmers it's a nightmare. Ever looked at the "class pyramid" monstrosities in Java? That includes the two primary GUI systems. Ever debugged all the stuff coming from the type erasure that you are stuck with doing in Java? And, let's not even gets started on the people who think they can write concurrent code (Hint: if your Java concurrency solution doesn't involve java.util.concurrent-you're doing it wrong.) While Java gets many things right, it's ability to prevent people from falling into pitfall after pitfall is NOT one of them. I would argue Clojure does a much better job at this simply by the fact that immutability is much more of a default choice.
- sqeaky 10y agoWhy do people here try so hard to be condescending sometimes? Laughing and mocking does not help when prior to it others tried to provide earnest replies, based on experience and presumably reality. Please save the vitriol for arguing with those reason does not work on. Your points do have merits in showing Java is imperfect. I would even say that it is deeply flawed. I would not agree it is impractical. The simple fact about the practicality of Java can be shown in the millions and millions of lines of code powering much of the boring business infrastructure in the United States. If Java disappeared overnight Banks, Insurance companies and much of the rest of the financial sector wouldn't open the following morning (And then we look at our phones).
- Jach 10y agoThe same would happen for COBOL. That alone doesn't make it practical or show its practicality, it only shows that it has actually been used in critical systems, apparently with some success. Many programming languages can claim that. Honestly had the same reaction as above. It's a hilarious laughable claim that the Java community gets this nebulous "practical concerns" thing right. Agree it's not a helpful response here but I don't blame them for falling to it. It's not exactly "forced". Probably better for @shit_hn_says though. Edit: the much less nebulous claim by 'tlarkworthy is much better.
- agumonkey 10y agoah nesting, threading and monads.
- ryuuseijin 10y agoThere is a neat macro I've been using to solve problems like the nested if-lets in the post: https://github.com/Engelberg/better-cond https://github.com/Engelberg/better-cond (b/cond :let [x (foo)] (not x) (do (log "foo failed") false) :let [y (bar x)] (not y) (do (log "bar failed") false) :let [z (baz x y)] (not z) (do (log "baz failed") false) :else (do (qux x y z) (log "it worked") true))
- whalesalad 10y agoHave you looked into the `some->` threading macro? https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/some-> https://clojure.github.io/clojure/clojure.core-api.html#cloj...
- ryuuseijin 10y agoYes, it gives you short circuiting on nil values. The post mentions `some->` and laments its inability to handle varying function signatures. You can combine `some->` with other threading macros to make it achieve the desired effect, and you can also achieve the desired effect with `if-let` as the post demonstrates, but I believe the b/cond approach to be more readable than both.
- whalesalad 10y agoI want to echo comments here by sharing that my professional experience with Clojure has been dismal. It's definitely unfortunate because Clojure itself is a wonderful language. To give you a better idea: no use of component or similar, no ability to run the app 100% in a repl. A junior engineer prematurely split a monolith out into microservices that weren't truly isolated in their function (they shared the same underlying db and a handful of "libraries" that were not libraries) -- a true fucking productivity nightmare. Shipping a small fix would oftentimes involve version bumping and deploying 3 separate jar's before you could even QA it. You NEED to use your repl and reloading tools when doing Clojure. You can't rely on your test suite, jenkins, or suffer through JVM restarts every few minutes. When people do not follow these concepts Clojure becomes an absolute fucking nightmare to work with. There is a stark difference between programming in a functional/immutable style and using a functional language. You can write imperative and procedural style programs in Clojure just as easily as you can write functional programs in Python. How can we fix the Clojure ecosystem? It will be hard. Lisp is oftentimes an esoteric environment. You don't see a lot of Lisp/Clojure (correct me if I am wrong) in environments where you need to be pragmatic and move quickly. There also isn't a "rails" for Clojure. With the exception of Lein (which is a truly remarkable contribution to the open source community) -- there isn't a whole lot to use as a metal model or better a popular style of building and organizing your apps. It's all the rope to hang yourself with -- and if you aren't an experienced hacker you're gonna hang yourself. Finally: I find it really tough to find solid use cases for Clojure as opposed to something else. The real things it has going for it are java interop and easy concurrency. I usually have a very hard time choosing Clojure over Python because of how quickly I (personally) can use Python to build extremely readable, modular, single-responsibility, functional, etc... code.
- pivo 10y ago> you don't see a lot of Lisp/Clojure (correct me if I am wrong) in environments where you need to be pragmatic and move quickly. I don't know if you're wrong but isn't that the opposite of what PG claimed in his famous "Beating the Averages" essay? http://paulgraham.com/avg.html http://paulgraham.com/avg.html
- johnbellone 10y ago
- dfan 10y agoSeasoned Clojurians: is (every? #(= % “success”) (map :status (:state task))) actually considered crappy code? It looks perfectly natural to me, but my Lisp background is more generic.
- shady-lady 10y agocrapiness comes more so on the readability/presentation aspect.
- SomeHacker44 10y agoI had the same reaction. I much prefer this to the threading macro.
- weavejester 10y agoNo, not at all. The threading macros are more useful when you have more nested code. For example: (->> coll (remove nil?) (filter :available?) (map :stock) (reduce +)) Is perhaps a little easier to read than: (reduce + (map :stock (filter :available? (remove nil? coll))))
- mrbrowning 10y agoI really enjoy Clojure. Persistent data structures, core.async, and transducers have all been really pleasant abstractions to work with. I've been writing in it for fun and utility for maybe five years now, and have used it for one mid-scale contracting project, so I hope that that's sufficient background to prevent dismissal of the following as the gripes of a beginner, but: it's so strange to me, for all of the appeals that Clojure makes to an interactive development process involving the REPL and immediate feedback, that a first-class debugging experience on the level of Common Lisp's doesn't seem to be on the roadmap at all. The type of explorative developing you do in that environment, with the ability to drop into the debugger on error, alter the function body and restart the frame with your new code, seems about as far beyond the standard Clojure workflow as REPL-based development is beyond the save-compile-fix cycle of less interactive language ecosystems. I can deal with the Java stacktraces and the occasional mystery of which concrete class is actually backing IPersistentMap after however many invocations of assoc, but if the experience of writing code is going to be a pillar of the language's sales pitch, why isn't this sort of next-level debugging experience on anyone's radar? The CIDER debugger is nice, but it's not CL nice.
- hcarvalhoalves 10y agoThere's a tracing debugger for CIDER (http://bpiel.github.io/sayid/ http://bpiel.github.io/sayid/) and Cursive plugin for InteliJ integrates a step debugger (https://www.youtube.com/watch?v=ql77RwhcCK0 https://www.youtube.com/watch?v=ql77RwhcCK0), but let's agree matching CL's debugger is a tall-order - it's extremely nice and only deals w/ CL, while Clojure, being a hosted language, cannot hide the underlying implementation (interop calls are so common and a big selling point of Clojure in the first place).
- mrbrowning 10y agoYou're right, and I ought to have mentioned the difficulty of implementing something like that for Clojure. So maybe the issue is more holistic, which is that it's hard to tell how it's positioned: it's not quite the sort of language that drills as deep as possible into the affordances of being a Lisp, but it's also not quite the sort of language that does everything just well enough, given its investment in, for example, immutable data structures. That doesn't stop me from using it, or presumably anyone else who gets value out of using it, but I sort of idly wonder what the cases are that it's uniquely suited to. It might not matter: maybe being the obvious right choice for some use case in the abstract isn't that important, and contextual factors re who's doing the work and how they think are just as relevant to choice of language. Still, it seems like we tend to talk about languages now in terms of their most indispensable applications, for better or for worse.
- stewbrew 10y agoWhat's the state of static type checking in closure? Is it accepted among the community? My uninformed impression is that runtime checking via specs won?
- aisofteng 10y agoI think that there is something more fundamental underneath this experience report that is worth remarking upon but that isn't mentioned in the article. >If I were going to give you a quick summary of what our codebase is like, I’d say it’s procedural code that manipulates maps. >One thing we do use more extensively are multimethods. We use this to dispatch asynchronous jobs in our workers, to fan out to different handlers from webhook endpoints, and to make transitions in our deploy finite state machine. >... sometimes you find yourself boxed into writing non-idiomatic Clojure. A good example of such a situation is dealing with a morass of heterogenous functions that can return error codes. Languages based on different paradigms excel in different situations. Lisp-like languages such as Clojure (and, more generally, functional languages), have abstractions perhaps most well suited to "pipeline" situations - take an input, apply transforms A, B, C, ... in sequence until the output is returns, bailing out if there is an error in some stage. Multimethods, which the post mentions, are a good example of this: given input X, send it off to Y based on some conditions. In software applications which have to do this sort of work, it's a good fit. If there is complex state to be managed under many different conditions, however, these paradigms can be cumbersome to deal with. >It is unclear to me if the category theory would still be a win on a less experienced team. I have a long history of being skeptical of things like this, but it has improved our lives recently. The author mentions realizing that the Either monad well embodied the nature of the algorithms being implemented. Speaking from my own experience, I would suggest that teaching a team implementing the sort of software it seems the author's team was implementing would be a worthwhile investment - but only if it was reasonably clear beforehand that those abstractions would be well suited to the problem at hand. Perhaps the most important skill to train a team up on, in my opinion, is the ability to recognize the type of problem at hand well enough to see what abstractions would be well suited to it and thus what language to use. In other words: it is very important to choose your tools well. If I could offer some advice to the author (and others in similar positions) regarding the question of investing time in training a team in any set of abstractions, be it concepts in category theory for the sake of Clojure or something else, it would be this: analyze the nature of the problem at hand, make a decision on what tool (language, framework, whatever) you feel would be best suited, familiarize yourself with its world if you haven't already, and then have a meeting with your team in which you concisely explain that analysis and the reason behind your decision. Mention why other choices would not be as well suited, but, CRUCIALLY, also give examples of situations in which the other tools you considered would have been the best solution. If you have and reservations about your choice, explicitly state them. (In fact, you may be surprised by someone on the team having had experience in the domain at hand but not having mentioned it because it hadn't become relevant yet!) This will not only teach the reasoning process but also give the team some information about what alternate approaches to consider when faced with a problem unlike the ones they have seen before, especially if the team is junior. Furthermore, sometimes one discovers that the nature of the problem being worked on is, in fact, different from what it was originally understood as. It is a lot to place on one lead to always be vigilant about that, and self-doubt can creep in. If you transparently expose your thought processes to your team, you may find yourself surprised by eventually having a team member saying to you, "don't you think that this other tool/approach you had considered may actually be better suited to <situation> now"? It is a lead's job to lead, but it is also to share as much of their knowledge, experience, and reasoning process as possible with the team. Sometimes we go down the wrong route at first, but everyone on a team should understand the why and what of every point along the way. As a lead, I make it my goal to train everyone under me well enough to replace me and to feel comfortable enough to voice any commentary on the decisions I have made. I was going to go on, but I think I went off on a bit of a tangent. I'll leave off here.
- StreamBright 10y agoClojure is the default go to language for me over the last 5 years. It is not even funny how more productive Clojure is compare to Java if we are talking about JVM hosted languages. There are obviously shortcomings of Clojure, missing Either and so on, but you can always implement these simply, worst case with a macro. It is good to see libraries like cats though.
- dghf 10y ago> Nevertheless I definitely emitted some crappy code in my first few months. Stuff like: (every? #(= % “success”) (map :status (:state task))) > Which I’d write like this today: (->> task :state (map :status) (every? #(= % “success”))) While I love threading macros, I'd argue in this case readability and comprehensibility between the two examples is more or less a wash. Personally, I find the former slightly easier to grasp at first sight. The second could be improved (again, in my personal opinion) by combining the first two lines: it seems unnecessarily granular to say, essentially, "we take a map called task, and then we get the value of its :state attribute", rather than just "we get the value of the :state attribute of the map called task". Both could be made terser via the set-as-predicate idiom: (->> (:state task) (map :status) (every? #{“success”}))