1. 26
  1.  

  2. 10

    At my job we develop applications in Clojure to be deployed on a JVM platform. Our experience jibes with Norvig in that we can write a complete program in Clojure that is around 90% smaller than the equivalent Java code. A program that might require dozens or even hundreds of Java class files can be written in one or two Clojure modules.

    I do agree with @moodyharsh that optimizing for understandability is probably a better goal than reducing up-front development time. The applications we deliver are expected to be in service for many years, with the oldest of these at 8+ years and counting. There are now around 200 people who program in this environment and any of us might be expected to maintain or modify code authored by another. In aggregate they are an average group of programmers. (But highly adept at delivering business value—most just aren’t formally trained in CS or software engineering.)

    I would say that optimizing for code size isn’t generally advantageous when you’re talking about minor reductions in lines-of-code, but I think there’s a clear benefit to choosing a language that affords an order-of-magnitude reduction. (If I had my way, we’d all be programming in APL, which I’ve found to be another 10x more expressive than Clojure, but that’s for another time.)

    As for readability, I don’t believe that the syntax makes for a significant impediment although the choices made in formatting can have an effect. Rather, I think the major challenges arise from complex business logic and data modeling. The lightweight syntax and operators provided by Clojure for modeling ad-hoc data as vectors or hash maps are far simpler than Java, comparable to Python which is the language the majority of our staff has the most prior experience. But I’ve not found that the choice of language really makes a big difference in simplifying the business logic. A poorly designed program will be hard to understand or modify regardless of its implementation language.

    One area of particular challenge for us is reasoning about the asynchronous parts of our programs. Thus far, I haven’t seen any language that makes asynchronous programming as simple as it needs to be for the average business programmer.

    1. 2

      One area of particular challenge for us is reasoning about the asynchronous parts of our programs. Thus far, I haven’t seen any language that makes asynchronous programming as simple as it needs to be for the average business programmer.

      Might be worth checking out Elixir. It has a rep for being Ruby on the BEAM, but the language actually has Clojure and (obviously) Erlang influence.

      1. 2

        What kind of asynchronous programming are you referring to? I find there are two kinds of asynchronous behavior I need to reason about: async-as-implementation-detail and async-as-business-logic.

        On the implementation side, I like Kotlin’s approach, which is to default to awaiting unless the code says not to. Flipping the default behavior is a huge quality-of-life improvement. I dislike writing asynchronous code in languages like JavaScript where nearly every line of your code starts with “await” and if you leave it out by mistake, you’ve probably just introduced a hard-to-diagnose intermittent bug. Await-by-default means you can write async code as if it were synchronous.

        But the business logic side is the tricky one. Event-based architectures have this a lot and it can be a pain to work with. Event A triggers event B which triggers event C, all indirectly delivered through a message queue, so tracing the source of a weird value in event C is never as simple as just looking at a stack trace.

        1. 1

          Mostly implementation details, such as a call out to an API service or database query that returns a future. A lot of the trouble comes around deciding how soon in the program can we schedule the future, and how far later can we wait to block on its result, while maintaining understandable code that doesn’t spread its concerns all over he place.

      2. 6

        My team switched to Clojure from Java for around a decade now and we’re very happy with our decision. I copresented on on of the applications my team develops and some of the benefits we got from using Clojure that would be difficult to achieve with traditional languages like Java.

        1. 3

          90% of the time is spent in reading code. Maybe we need to optimize for that instead. The indentation syntax chosen by lispers make it difficult to see scope and the 2 space indentation makes me want to stab my eyes. I like the kebab-case though. Long back I tried messing with begin / end instead of parens and found the resultant code much better and ruby-like.

          Lisp sorely needs a coffeescript, an external DSL as opposed to internal macros along with the appropriate tooling. JavaScript is really pragmatic in this regard. Why do lisp programmers hate lexers and parsers ? Until that happens Lisp will never be a real alternative to anything - again, 90% of time is spend in reading code. I have always had a soft-spot for dylan.

          1. 7

            The indentation syntax chosen by lispers make it difficult to see scope and the 2 space indentation makes me want to stab my eyes.

            I feel the same way reading Java. This has everything to do with familiarity and basically nothing to do with any inherent superiority in either style.

            Lisp sorely needs a coffeescript

            These kinds of dialects get created on a regular basis. It’s honestly very predictable if you follow a lisp mailing list. In the end no one uses them because the advantages are not very compelling; they only seem that way to people who haven’t spent much time using lisp.

            1. 1

              In Lisp parens are overloaded to represent

              1. Assignment
              2. Block Scope
              3. Expressions / Statements
              4. Function calls
              5. Function body
              6. Macro body
              7. Data structures

              When looking at a piece of Lisp code how quickly can you tell each of the above apart ?

              Lispers can adopt this style for better representing block scope

              (let [x 5]
                  (if x
                      (do
                          (print y)
                          (+ x y)
                      ) ; end-do
                  ) ; end-if
              ) ; end-let
              

              One use of Lisp’s regular parens, it actually makes the code easier to read because you don’t have to remember all the syntax. What I object to is the paren placement. where cuddling them together makes the scope not clearly visible. Can I use 8 spaced indentation in Lisp without the entire lisp community complaining ? I don’t think so.

              Let us look at how absurd the situation is. Because of the paren syntax - people are using XML, python and compiling JavaScript to a lisp like vm bytecode to avoid Lisp. One advantage of XML is the use appropriate beginning and ending tags. which makes it easy to tell things apart. I would bet you that if you take into account the reading time / comprehensibility time as opposed to lines of code in a large piece of code - Python/XML/C-like syntax would win.

              1. 4

                Your list of seven things includes a lot of duplicates. #1, #2, and #5 are just special cases of #6. It’s unclear what #3 “Expressions/statements” means since statements do not exist in lisps. It’s true that for #7 older lisps (CL, Scheme, and Emacs) certainly do have this problem of using parens for too many things, but newer lisps use other types of brackets. In Java, parens are used for two things: calling methods, and grouping operands in math expressions. So it’s not as if overloading the meaning of delimiters is somehow unique to lisps.

                You say that it’s difficult to read, and I say that it’s easy to read once you have experience. One of us has experience, and one of us doesn’t. Not really sure where else the argument can go from there.

                1. 1

                  #1, #2, #3, #5 are not special cases of #6 in terms of side-effects they cause.

                  1. 1

                    FWIW, I believe that define in Racket could be considered a statement since it raises a syntax error if used in an expression position.

                  2. 2

                    When looking at a piece of Lisp code how quickly can you tell each of the above apart ?

                    About as quickly as I can scan it, honestly. The keywords like if, let, and do are more significant in understanding it, but I take your meaning that finding the the close paren can be hard. I have seen Clojure beginners mess up their code by placing the close paren incorrectly. Most good editors have matching paren highlighting to make this easier.

                    BTW, an experienced Clojure programmer would code this up more tersely, something like:

                    (when-let [x 5]
                      (print y)
                      (+ x y))
                    
                    1. 1

                      The keywords like if, let, and do are more significant in understanding it,

                      Yes thats what I mean. If I look at a blob of any non-lisp code … I can immediately say where the conditionals, loops and function calls are. For lisp I have to go through each line to get the idea.

                       // If C programmers wrote like lisp
                       for (i = 0; i < 10; i++) {
                           if (i % 2 == 0) {
                               doSomething (i); }
                           else {
                               doSomethingElse (i); } }
                      
                      

                      You can’t add new statements without messing up “{” and “}”. The same happens in lisp if I want to add more statements in cond blocks. Lisp also forces programmers to not use standard control flow and favors recursion all times, all these hinder readability.

                      1. 4

                        To me this reads like a combination of a lack of literacy in the language (although in Clojure for is a loop keyword and if is a conditional—just like in C) and a mindset favoring imperative programs (based on your use of the term “statement.”) To be clear, I don’t think imperative is a “wrong way” of thinking but it will be an impedance to effectively using a Lisp language where all syntactic forms—including loops and conditionals—are expressions that have some value when executed.

                        I’m unsure what you mean by standard control flow but I rarely find myself writing recursive programs on finite data in Clojure or any other language. I agree with you that a recursive program is probably harder to understand vs the the equivalent iterative program in many instances.

                    2. 1

                      This is also a point where I find C-style syntax lacking. Other languages have for/next, do/od, if/fi or if/endif where the ending bracket must match the beginning bracket. In C, Java, Go, you often just find yourself at a big stack of {{{{.

                  3. 5

                    Lisp sorely needs a coffeescript, an external DSL

                    That wouldn’t be a DSL as it wouldn’t have anything domain specific about it. Quite the opposite.

                    Alternative syntaxes do exist and are used by a few. The general consensus over the 50 year old discussion about them, is that the vast majority of lispers do perfectly fine without them.

                    I’d say that, as any other language, it rather need practical selling points and marketing by some invested company, rather than theoretical/technical merits.

                    1. 4

                      The whole point of using Lisp instead of some other modern high level language is that it supports macros and is homoiconic, so you can write macros in Lisp itself instead of in a different macro language. If you get rid of this, why not just use Ruby? I’m not as familiar with Dylan - how easy is it to write macros there vs. in a more traditional lisp?

                      1. 3

                        90% of the time is spent in reading code. Maybe we need to optimize for that instead. The indentation syntax chosen by lispers make it difficult to see scope and the 2 space indentation makes me want to stab my eyes. I like the kebab-case though. Long back I tried messing with begin / end instead of parens and found the resultant code much better and ruby-like.

                        IME reading more code and becoming more familiar with the style makes different formats easier to read. In any case, there’s no requirement that Lisp use 2 spaces.

                        Personally, I use the default Emacs indentation for everything, and don’t have any problems reading Lisp, C++, Javascript, Python, or any of the other languages I have to use.

                        Lisp sorely needs a coffeescript, an external DSL as opposed to internal macros along with the appropriate tooling. JavaScript is really pragmatic in this regard. Why do lisp programmers hate lexers and parsers ? Until that happens Lisp will never be a real alternative to anything - again, 90% of time is spend in reading code. I have always had a soft-spot for dylan.

                        To be blunt, I suspect JavaScript programmers are quick to use non-Javascript whenever they can because Javascript is unpleasant to work with.

                        As far as why Lisp programmers prefer using macros and DSLs, it’s mainly because it’s so convenient to use one language and set of tooling for everything. Why complicate things with five different tools when one can do the job equally well? And it means anybody who knows the language can jump to the source code and see what’s going on.

                        As you said, reading code is the important thing, so why not spend your time reading and understanding one language instead of a bunch?

                        1. 1

                          It’s also worth pointing out that by far, the most commonly-used “Javascript+” language is Typescript… which is just Javascript with types. Same syntax.

                        2. 1

                          I was going to mention Dylan here until I saw your ending paragraph.

                          1. 1

                            90% of the time is spent in reading code. Maybe we need to optimize for that instead … The indentation syntax chosen by lispers make it difficult to see scope

                            I agree about the importance of optimizing for readability. I’m not sure exactly what you mean re: “indentation syntax”, but one of the things I absolutely love about programming in Racket (a lisp) is how lexical scope is handled with parenthetical blocks, and how easy it is to move lexically scoped blocks around in the editor.

                            Readability is very subjective, so I’m not trying to invalidate your view of lisp readability, but having scope neatly contained in the structure of the code is a very nice feature for me.