[HN Gopher] Why Clojure? (2018)
       ___________________________________________________________________
        
       Why Clojure? (2018)
        
       Author : bribri
       Score  : 119 points
       Date   : 2021-01-03 16:06 UTC (6 hours ago)
        
 (HTM) web link (briansunter.com)
 (TXT) w3m dump (briansunter.com)
        
       | [deleted]
        
       | swayvil wrote:
       | Gratuitous parenthesis nesting. We see it a lot in Clojure.
       | 
       | Nesting boxes instead. It could be represented, in the editor,
       | that way.
       | 
       | It could be much more legible.
       | 
       | Is anybody doing that?
        
         | appleflaxen wrote:
         | they are logically equivalent (of course) and the parentheses
         | become invisible once you are used to them. Every language has
         | a threshold of "fluency" which, once you reach it, makes it
         | easy to understand at a glance.
         | 
         | Retooling to use nesting boxes has obvious appeal to a
         | newcomer, but none to an existing programmer, and the cost to
         | the corpus of existing programmers is far too large to justify.
        
       | johnday wrote:
       | Now I'm somewhat biased, but that complete list of advantages
       | also applies to Haskell. [^1]
       | 
       | In fact, nearly all of the claims made about Clojure here can be
       | made about haskell _more strongly_.
       | 
       | I've half a mind to do a direct comparison on every point. I'd be
       | interested to hear the author's thoughts on the similarities and
       | differences.
       | 
       | [^1] With the obvious exceptions of s-expressions, java/js
       | interop, and "subjectively good design".
        
         | didibus wrote:
         | Interop, REPL driven development, s-expressions, and great
         | support for working with maps seem to be the ones that don't
         | apply to Haskell from my quick glance.
        
         | vnorilo wrote:
         | I actually don't want to get into the argument over which is
         | better, but dynamic and static types do change everything, also
         | with regard to how those features in the list feel in a
         | language.
        
           | johnday wrote:
           | Agreed! Asking which language is "better" is a fool's errand.
           | But talking about specific upsides, especially in comparison
           | with other languages, is a useful reductivist tool in
           | determining how we can improve the story for each language.
        
         | cdmckay wrote:
         | The JVM interop is a huge positive for Clojure, in my opinion.
         | 
         | Being able to consume any JVM library makes Clojure usable in
         | many more professional settings than Haskell.
        
           | johnday wrote:
           | Yeah, that makes sense. If the killer app is JVM interop,
           | nothing but JVM based languages should even be on the table.
           | The "why Clojure?" question just becomes "because Clojure is
           | the best language _on the JVM_ ," which is not too
           | interesting of a topic IMO.
           | 
           | Haskell has decent interop with C/C++ languages, but
           | certainly nobody uses haskell _because_ of that.
        
             | andrewem wrote:
             | I think there are at least two cases where JVM
             | interoperability is relevant. The first is when a company
             | is already using the JVM and so knows how to deploy and
             | monitor it; in that case, it's easy to choose because it
             | presumably imposes limited burdens on the company's
             | operations people. The second case is when you want access
             | to some mature environment, so that all kinds of packages
             | and services are available to support it, but don't have a
             | commitment to any one in particular yet, and Clojure
             | qualifies because of the JVM; in that case, a non-JVM
             | language like Python, PHP, or Ruby might also work.
        
             | didibus wrote:
             | It's not just killer JVM interop, it's killer all around
             | interop. Clojure relies on interop more than any other
             | major language, the language has inherent syntax around it
             | and even its standard library is designed with it in mind.
             | That makes it very easy to adapt over different runtimes,
             | giving Clojure more reach than most. That's why you have
             | Clojure JVM, Clojure CLR, Clojure Unity, ClojureScript,
             | Clojerl (Clojure on BEAM), etc.
        
             | BoiledCabbage wrote:
             | If the killer app is practical usage than Clojure clearly
             | comes out on top.
             | 
             | The problem with function language adoption is people keep
             | pushing Haskell likes it's anything more than at its
             | essense a language exploration research project.
             | 
             | The fact that you'd have a comment that ignores a language
             | because it wins by default because it practical is more
             | proof that people evaluating languages are speaking two
             | different... well languages.
             | 
             | Some are looking for what they feel have the coolest ideas,
             | others are looking for languages with very cool ideas, much
             | better than what they're using today, and can still be
             | effective/ productive in. Haskell is cool if you want to
             | think about or play with ideas. Clojure is cool ideas, and
             | can still be productive in (ie full modern library
             | support).
             | 
             | As well if you don't know, F# falls into that same bucked
             | of cool ideas, better than your avg imperative language and
             | can still be very productive due to language and .net
             | library.
             | 
             | We're trending to a point where any non-systems language is
             | just an exploratory language unless it's build on JVM or
             | .Net.
             | 
             | Otherwise the produvtivity loss from lack of libraries is
             | almost impossible to overcome from any possible
             | produvtivity gains from the language.
        
               | mumblemumble wrote:
               | > We're trending to a point where any non-systems
               | language is just an exploratory language unless it's
               | build on JVM or .Net.
               | 
               | I certainly hope this does not turn out to be the case.
               | 
               | Both the JVM and .NET impose a certain type system on all
               | their client languages. Those languages have an option to
               | embrace it, like F# or Kotlin have, or to struggle
               | against it, like Scala has, but they don't have the
               | option to truly pull free of it. Not without shutting out
               | effortless interop with the rest of the platform, and, in
               | doing so, undermining the whole purpose of being on those
               | platforms in the first place. And, since most the
               | interesting developments in programming languages center
               | on type systems, that implies that huddling together on
               | the Big Two bytecode VMs stifles a lot of really
               | interesting innovation.
        
               | BoiledCabbage wrote:
               | > since most the interesting developments in programming
               | languages center on type systems, that implies that
               | huddling together on the Big Two bytecode VMs stifles a
               | lot of really interesting innovation.
               | 
               | Not disagreeing at all. Interop with a library means
               | interop/conformance with its type system. Which means if
               | you want languages with new / innovative type systems,
               | someone will need to build a library system to resolve
               | this.
               | 
               | Either an untyped library system as large as the .NET or
               | Java library systems, or a library that has a trivial way
               | to tack on type transformation of some form so that each
               | language that interops with it can minimally add a type
               | translation layer between the two. What that looks like,
               | I'm not certain.
               | 
               | But as long as engineers need to be productive, they need
               | a robust modern library system. No new lang will get
               | adopted if the lang author also needs to build up a
               | complete library system, so it must be a general
               | component.
        
               | mumblemumble wrote:
               | I'm hopeful that Rust will lead in a good direction.
               | 
               | A C-style ABI, by virtue of being the least common
               | denominator, is probably the best bet for re-usability
               | across paradigms. Higher level languages would want to
               | write idiomatic facades, but they already habitually do
               | that anyway, even on higher-level platforms like Java and
               | .NET.
               | 
               | And I think that deterministic memory management is
               | probably also a pretty important feature. You don't want
               | your libraries all bringing their own clever ideas about
               | object lifetimes and such. But you also need it to be
               | very reliable; a real danger with inviting libraries
               | written in C and C++ into your process space is that they
               | are liable to corrupt your memory. Rust's affine type
               | system seems like a big step in the right direction here.
               | 
               | Similar thoughts for the error model. I don't have any
               | particular complaints about exceptions, except that you
               | don't want to be bleeding them on an external API,
               | because that ends up being another spot where languages
               | can fail to mesh.
               | 
               | What's missing, though, is that there is no good cross-
               | platform standard for libraries that work with the C ABI
               | (neither in source nor binary form) for other languages
               | to plug into. So that's where I get to thinking that Rust
               | might be closer to (if not exactly at) the mark than C
               | is.
        
               | Scarbutt wrote:
               | While all of this is true and interop can save you from
               | being completely stuck in a problem on many occasions,
               | the interop is not as seamless as many advertise, at
               | least not in Clojure, don't know about F#. Many times in
               | Clojure is easier to write two wrappers, one in Java (to
               | make the Java lib access tolerable from Clojure lol) and
               | the Clojure one. And I'm talking about ad-hoc wrappers
               | for your use case/functionality needs, not general
               | wrappers, which take much more time. Productivity goes
               | down in this translation, you start to wonder why are you
               | wasting all this time wrapping Java libs. Kotlin is much
               | better in this area of course.
        
           | city41 wrote:
           | ClojureScript is another plus. I'm unsure what Haskell's
           | story is for frontend dev (I have no Haskell experience).
        
             | johnday wrote:
             | The GHC compiles Haskell either directly to assembly or via
             | LLVM. There is a GHCJS project, which works fine, but
             | personally I've not used it. There's not much of a story in
             | terms of frontend Haskell.
             | 
             | On the other hand, there are two "child" languages of
             | Haskell for the job: Elm, which is a frontend-focused
             | language, and PureScript, which compiles to JS and is
             | designed for that use case.
        
             | teodorlu wrote:
             | As someone who tried out GHCJS, then Purescript, then Elm,
             | then Clojurescript:
             | 
             | - GHCJS and purescript are powerful, but the learning
             | experience might be steep[1]
             | 
             | - Elm is an excellent entrypoint into ML programming in the
             | browser. Solid story for new users, and a great standard
             | library for interactive web applications.
             | 
             | - ClojureScript differs from Elm in that it embraces its
             | host, with all its power and all its wrinkles. Writing Elm
             | is mostly a smooth experience. Read the guide[2], then you
             | can actually build a web app.
             | 
             | I've spent the most time with Elm. Other people might have
             | different experiences.
             | 
             | [1]: a few years since I tried, might be better now. [2]:
             | https://guide.elm-lang.org/
        
           | fiddlerwoaroof wrote:
           | Yeah, I worked at a company where several Haskell projects
           | crashed and burned because of interop issues with existing
           | JVM systems, but the Clojure project I worked on did really
           | well.
        
         | teodorlu wrote:
         | I really appreciate both Clojure and Haskell. I agree that
         | 
         | > nearly all of the claims made about Clojure here can be made
         | about haskell
         | 
         | , but I'm not sure about `more strongly'.
         | 
         | ## DX with Haskell (and ML friends) I miss in Clojure:
         | 
         | - Harder to do sound, up-front design with data types and
         | module interface, where implementation becomes almost trivial
         | after when the type signatures make sense
         | 
         | - Consistency checks from compiler / type checker
         | 
         | ## DX with Clojure I miss in Elm/Haskell:
         | 
         | - REPL ergonomics, where every action I could think of makes
         | sense as a REPL command, leading to very small incremental
         | pieces.
         | 
         | - Excellent default data structures with literal representation
         | and serialization (EDN)
         | 
         | I'd love to read a point-by-point discussion of the article
         | sections comparing Haskell and Clojure, though that's much to
         | ask for in the comments field.
        
           | johnday wrote:
           | Really nice insight, thank you! Clojure has always been a bit
           | of a mystery to me, so this is interesting to read.
           | 
           | - REPL ergonomics, where every action I could think of makes
           | sense as a REPL command, leading to very small incremental
           | pieces.
           | 
           | This is the only point I would dispute. The GHC interpreter
           | `ghci` is very powerful and offers a lot of the same upsides.
           | Beyond this, the language server offers in-code evaluation
           | with "-- >>> expression" syntax, which is a cool new step
           | towards fast looping UX. Clojure is certainly great at this,
           | but I'd say Haskell is not far behind.
        
             | teodorlu wrote:
             | I've only dabbled with GHCI. I've used it as a standalone
             | REPL for trying out small things, the same way I'd use a
             | Python or Javascript REPL. I haven't used the REPL /the/
             | developer interface to the program. In Clojure, I would (1)
             | start a REPL server, (2) connect to it from my editor, and
             | (3) send expressions to it. I didn't develop Haskell that
             | way, though I think it was possible with Intero[1].
             | 
             | Within the Clojure community, there's a perception that the
             | Clojure REPL is one of its strongest selling points[2].
             | 
             | Are you using the REPL actively when developing?
             | 
             | Edit: really curious about the "-- >>> expression " syntax!
             | I might have to give Haskell another go.
             | 
             | Edit 2: Example of this interaction in practice with
             | VSCode[3]
             | 
             | [1]: https://github.com/chrisdone/intero#readme [2]:
             | https://clojure.org/guides/repl/introduction [3]:
             | https://github.com/haskell/haskell-language-server#features
        
               | remexre wrote:
               | This is definitely weaker than a SLIME-like REPL, but I
               | use ghcid --warnings --test My.Module.Tests.runTest,
               | where runTest is tasty's defaultMain, to get near-
               | instantaneous test running on changes to files. (Since
               | it's GHCi instead of GHC, waiting for a full compile is
               | unnecessary, and it still has the necessary smarts about
               | what needs reloading to avoid reloading the whole project
               | every change.)
        
               | d2v wrote:
               | Have you tried out ghcid? It basically just runs ghci on
               | your program every time you save, and gives an updated
               | list of errors and warnings. Not interactive in the sense
               | that you don't manually test your functions with it, but
               | like 95% of debugging in Haskell is just fixing errors at
               | compilation time. I find it to be a very nice developer
               | experience. Just need a text editor and a terminal with
               | ghcid open and you get immediate feedback as you program.
               | 
               | https://github.com/ndmitchell/ghcid
        
               | teodorlu wrote:
               | Haven't heard of it before, but this looks super
               | interesting! Thanks for the recommendation. I really like
               | the fact that my whole development workflow could be a
               | text editor and a terminal.
               | 
               | I enjoyed developing Elm with TCR[1] a while back; also
               | with an editor + a type checker (plus the revert part). I
               | recompiled my whole source on each save; incremental
               | recompilation should scale better.
               | 
               | [1]: https://medium.com/@kentbeck_7670/test-commit-
               | revert-870bbd7...
        
           | JackMorgan wrote:
           | I've done a non trivial amount of both Haskell and Clojure,
           | and this comment really hits the nail on the head. Each has
           | non-overlapping strengths that I miss when switching.
           | However, I've lately found F# to be my nearly perfect blend
           | of enough things that I regularly use that I mostly just use
           | that or Haskell when I'm messing about with katas.
        
             | teodorlu wrote:
             | As someone who hasn't written any F#, I'm curious about
             | F#'s advantages over Haskell.
        
         | fulafel wrote:
         | Compared to Haskell, Clojure, like Elixir and Erlang and
         | Scheme, is a much smaller and simpler language and is easier to
         | learn especially for people without a theoretical CS / math
         | background. This is due to Haskell's ambitious and powerful
         | static type system.
        
           | mac01021 wrote:
           | I'm not sure Clojure is _simpler_. I think Haskell may have
           | more regularity and less variety in language constructs.
           | 
           | I won't dispute that it is more alien to most programmers,
           | though, and that laziness and monadic IO require a bunch of
           | work to get used to.
        
       | lxtx wrote:
       | Language intricacies aside, is there a reason to use Clojure over
       | Elixir, Erlang? Genuinely curious what JVM has to offer vs BEAM /
       | OTP if you're going to use dynamic languages.
        
         | jwr wrote:
         | Practicality. Most of language "comparison" discussions miss
         | out on the practicality aspect: does it work, does it have a
         | good runtime (both true for Elixir and Erlang), can you write
         | code that runs both client-side and server-side, are there good
         | abstractions and libraries for many programming models, is it
         | being actively maintained and developed?
         | 
         | Clojure ticks all of those and more, while most superficial
         | comparisons concentrate on superficial aspects.
        
         | bcrosby95 wrote:
         | Clojure's version of immutability is more useful in some
         | domains than Elixir/Erlang's. E.g. you can both safely and
         | efficiently share memory in Clojure across multiple threads.
         | You can't really do the same in Elixir that I'm aware of - it
         | triggers a deep copy which can kill performance. Sometimes
         | acceptable, sometimes not.
         | 
         | Elixir/Erlang processes serve a lot of roles. If those roles
         | don't line up cleanly your code could end up a lot more complex
         | than necessary in other languages.
         | 
         | In the past the JVM had better raw performance, but I'm not
         | sure how much that might change with the new JIT in BEAM.
        
         | cdmckay wrote:
         | The JVM ecosystem is many orders of magnitude larger than than
         | BEAM/OTP.
         | 
         | For example, you can usually find an API library for Java even
         | from small vendors. I don't think I've seen any vendor provide
         | an Erlang API library.
        
           | macintux wrote:
           | Basho did for Riak, but that _might_ be due to the fact that
           | Riak was written in Erlang...
        
         | dimitar wrote:
         | There is this interesting thread over in reddit about it:
         | https://www.reddit.com/r/Clojure/comments/5q2gmi/convince_me...
        
           | lxtx wrote:
           | Thanks! Although I don't agree with all of the points, it was
           | a good read.
        
       | macmac wrote:
       | I would agree with all of these points except "pattern matching".
       | Yes several libraries implement it, but it is not built in and
       | even the best libraries feel clunky compared to for instance the
       | built in destructuring. Rich explicitly rejected pattern matching
       | ala ML for the reasons provided here:
       | https://gist.github.com/reborg/dc8b0c96c397a56668905e2767fd6...
        
         | bmitc wrote:
         | I wish he gave more examples in that response because I'm
         | generally confused what he's talking about. I'm familiar with
         | Racket and F#, but not having used Clojure, I'm missing some
         | context about the Clojure ways of doing things and examples of
         | the problems he claims.
         | 
         | > I feel about them the way I do about switch statements -
         | they're brittle and inextensible.
         | 
         | That is not the case in a language like F# or OCaml. I do note
         | that F# was introduced only slightly before Clojure was, but
         | pattern matching provides nicely extensible functions and are
         | anything but brittle in those languages. Also, active patterns
         | in F# allow one to extend the pattern matching functionality.
         | 
         | > The binding aspect is used to rename structure components
         | because the language throws the names away, presuming the types
         | are enough. Again, redundantly, all over the app, with plenty
         | of opportunity for error.
         | 
         | I'm not sure what he means here. Again in a language like F#,
         | names of the data aren't thrown away. They are pattern matched
         | against, only being "thrown away" to do actual calculations.
         | Nothing is ever lost where the data came from. For example:
         | type Shape =             | Circle r             | Square s
         | let area shape =             match shape with             |
         | Circle r -> System.Math.PI * r * r             | Square s -> s
         | * s
         | 
         | There's no confusion here. In fact, pattern matching in a
         | language like F# allows one to completely remove the
         | possibility of error. For example, this really shows off in
         | parsing applications. Once your parsing function returns a type
         | that can be pattern matched, it's extremely difficult to have
         | an error in the pattern matching sections of code. These are
         | typically the most robust parts of the application.
         | 
         | > I'd much rather use (defrecord Color [r g b a]) and end up
         | with maps with named parts than do color of
         | real*real*real*real,
         | 
         | > and have to rename the parts in patterns matches everywhere
         | (was it rgba or argb?)
         | 
         | I don't understand this either. In F#:                   type
         | Color = { R: float; G: float; B: float }              let
         | colorFunction { R=r; G=g; B=b } = r * g * b
         | 
         | No names are thrown away. Also, the comment on rather using
         | maps seems to assume the data type for every element of the
         | data structure is the same. How do you just use maps when the
         | underlying types of your record aren't the same?
        
           | adamkl wrote:
           | > I feel about them the way I do about switch statements -
           | they're brittle and inextensible.
           | 
           | What is meant by this statement is that pattern matching
           | violates the open/closed principal. If you add a new type to
           | switch on, you need to update all the pattern matching code
           | in the whole application to account for the new type.
           | 
           | It's one of the two sides of the "expression problem"[0] (the
           | other being object oriented polymorphism).
           | 
           | Clojure's approach to this is to use "multi methods" which is
           | sort of a "pattern matching"/"strategy pattern". You are free
           | to add in a new implementation of the multi method without
           | having to update existing code
           | 
           | Here is a great post that talks about Clojure's approach to
           | polymorphism and covers multi methods in detail:
           | https://aphyr.com/posts/352-clojure-from-the-ground-up-
           | polym...
           | 
           | [0] http://wiki.c2.com/?ExpressionProblem
        
             | sweeneyrod wrote:
             | I think the artificial shape example does not adequately
             | show how variant types/pattern matching are actually used
             | in languages like SML/F#/Haskell. It combines two different
             | cases.
             | 
             | Suppose you have a type `colour` defined as `RGB(int, int,
             | int) | HSL(int, int, int)` and then you add representation
             | as CIE. Then having to update each match on a colour is
             | absolutely a feature not a bug. If you miss some out then
             | your code will be wrong.
             | 
             | On the other hand, suppose you have various ways of
             | serialization (JSON/XML/s-expressions). In this case, it
             | would probably be nice if you could add a way to serialize
             | to e.g. protobufs without having to jump around your
             | codebase and all its clients fixing type errors. But in
             | most languages of the kind we're discussing, you can do!
             | You just have to represent the different serialization
             | methods in some way other than a variant type. For
             | instance, in OCaml you could just use classes and
             | inheritance (although in practice you probably wouldn't
             | because the language provides nicer tools).
        
             | bmitc wrote:
             | Thanks for the link to Clojure's polymorphism. I'll need to
             | read through it later and in more detail.
             | 
             | Isn't the open/closed principle more of an OOP design
             | concept? In a statically typed language like F#, I _want_
             | and expect to be notified what functions I need to update
             | when I add a new type constructor to an existing type. This
             | isn 't a problem and is welcomed. Just because one updates
             | functions doesn't make them brittle or inextensible. By
             | only adding a new pattern matching branch, one is able to
             | extend a function without affecting the other branches.
             | However, this is getting into the statically typed nature
             | of F#.
             | 
             | I think that link explains the expression problem rather
             | superficially. It says you just need to add a new class,
             | but neglects to mention that that also entails adding the
             | new method overrides. So simply saying the OOP way is easy
             | and functional programming is difficult when adding a new
             | type is not really accurate. Same thing for adding a
             | function in the functional programming paradigm, because it
             | neglects you need to add a branch for all types. In
             | reality, OOP inheritance and functional pattern matching
             | are simply transposes of each other, and I'd argue that one
             | is not really necessarily better or worse than the other.
             | They're simply different organizational methods of how the
             | data and functions on the data are organized.
        
         | fiddlerwoaroof wrote:
         | I always found core.match really nice: but, in general, I
         | strongly prefer using multimethods for the sort of thing other
         | languages use pattern-based dispatch for.
        
       | bernardv wrote:
       | This laundry list of features still does not tell me why I should
       | be using clojure over any other language.
        
         | lbj wrote:
         | Well, thats true but its implied.
         | 
         | The entire syntax for Clojure fits in a single line. Its easier
         | to learn and being as expressive as it is, the core idioms are
         | quickly picked up as well.
         | 
         | So - You can get into it quickly, very quickly if you're
         | already familiar with FP.
         | 
         | The brevity of the code means that you'll produce much more
         | robust code, which takes up a lot less screen real-estate. This
         | allows you to grasp the functionality of any code you read,
         | very quickly and start working on the problem.
         | 
         | It'll go as fast as Java, but slower than C/Rust. For some
         | performance oriented tasks, you'll have to put in more work
         | than makes sense, to get the performance you want. But for 99%
         | of the Apps that are being written, Clojure will perform just
         | fine and you'll end up with better code.
         | 
         | Compared to Haskell or most other FPs (not F#) you get the
         | added benefit of being on the JVM. Write once, run everywhere.
         | Huge libs to do everything from 3d graphics to webserving.
         | 
         | In most cases, I use Clojure for the above reasons summed up in
         | this one sentence: I makes me more effective than the
         | alternative.
         | 
         | ps: Having enjoyed Lisps for 20 years or so, Ive never used
         | Par-Edit :)
        
           | p0nce wrote:
           | > The entire syntax for Clojure fits in a single line.
           | 
           | But some of us absolutely don't like this syntax. The small
           | bits of Java in the article are very readable in comparison.
        
             | jhomedall wrote:
             | Nobody likes Lisp syntax initially. That goes away after
             | working with it for a short while.
        
         | didibus wrote:
         | I use Clojure because I find it more more fun and interesting
         | to program in. As a bonus, it happens to also be practical,
         | robust, productive and safe; with great tooling, a huge
         | ecosystem, reach to the browser, server, command line, and
         | desktop/mobile, good performance, good scale, and an awesome
         | community.
        
       | city41 wrote:
       | To not go absolutely insane with Lisps, you need some kind of
       | parentheses tool with your editor such as ParEdit. The up side is
       | once you get used to your tool, you can do really cool things and
       | navigate/change code in higher level ways. But the massive
       | downside (in my experience), is convincing coworkers to adopt a
       | Lisp is practically impossible. The language itself being so
       | foreign, and when they ask about the parentheses bringing up a
       | tool they need to learn on top of the language is a double
       | whammy.
        
         | schmooser wrote:
         | I'm using lispy[1] instead of paredit and absolutely happy
         | about it.
         | 
         | [1]: https://github.com/abo-abo/lispy
        
         | agumonkey wrote:
         | Yes, I'm a solid lisp fan, but anytime I have to edit lisp
         | without paredit I die.
         | 
         | Now paredit has been around for decades (1991 ? I forgot) so
         | it's not like it's rarity.
         | 
         | Now that said, a little bit of paredit-fu allows for some funky
         | coding sessions.. you can swap sexps, move blocks up down the
         | tree .. you can even process the sexp with some elisp code in
         | emacs.. it's very very swift.
         | 
         | What annoys me is the people who never expanded their knowledge
         | beside a few paradigm .. they'll stick with python only or js
         | .. or cpp. They're stuck onto a few libs and syntax.. it's a
         | pity.
        
         | jwr wrote:
         | While I would agree that paredit in Emacs is fantastic, the
         | real shock comes when you have to go back to editing code in
         | those languages that have the weird arbitrary punctuation. I
         | mean, your editor can't even properly manipulate those
         | expressions most of the time. In order not to go absolutely
         | insane when dealing with JavaScript, C++, Java, you need
         | absolutely top-notch editor support, and even then you can't do
         | everything that paredit does.
         | 
         | This gets even worse with languages where indentation matters
         | (Python, and the horrible abomination that is YAML) -- which
         | aren't even auto-indentable, because the editor has no idea
         | what you actually mean. I'm not sure if you can avoid going
         | insane with those.
        
           | onetom wrote:
           | You can get quite close to "AST editing"-like experience if
           | you just use the expand/shrink selection feature of a
           | JetBrains IDE. It supports most mainstream languages.
           | 
           | You just select a syntactic unit with opt-up/down, then u
           | either type over it, copy/cut/past or press left or right to
           | go to the beginning or the end of the selection, hence using
           | the selection as an intermediate step achieving AST-level
           | navigation.
           | 
           | And of course you can teach Emacs to behave similarly, using
           | the Expand Region (https://wikemacs.org/wiki/Expand_region) +
           | some hacks, but I agree, that smartparens also solves this
           | problem quite well.
           | 
           | UPDATE: also use avy-jump for emacs or acejump for intellij:
           | https://plugins.jetbrains.com/plugin/7086-acejump these in
           | combination with expand/shrink-region operations are a
           | significant productivity boost, which is easy to learn and
           | teach.
        
           | ojnabieoot wrote:
           | You can do indentation with the same semantic rigor as
           | parentheses - the problem with Python is less that it's
           | whitespace-significant and more that it's procedure-based
           | rather than expression-based. Parenthesis might mitigate this
           | somewhat to the human eye (at the cost of some clutter and
           | compromised immediacy), but unlike a LISP the Python
           | interpreter doesn't really understand what an expression is.
           | 
           | By contrast, I never have these kinds of semantic issues in
           | whitespace significant languages like F# - I make mistakes
           | that I might make less often in Scheme, but the compiler
           | usually sets me straight since it realizes a branch isn't
           | returning a value, etc.
           | 
           | In my view parentheses versus whitespace is really a judgment
           | call based on how your eyes read the source code. The crucial
           | thing is having something like s-expressions.
        
           | jb1991 wrote:
           | This is a common argument, but I think your mileage may vary.
           | I had a Clojure job for many years, and it was the only thing
           | I wrote in that time. When I moved to a different job in a
           | more conventional language, I also had that initial
           | frustration from the syntax context switch in my head. But it
           | was very short-lived. Now I find that there's really no such
           | frustration, it's just a different way of writing code, going
           | from either direction to the other requires some getting used
           | to it.
        
             | didibus wrote:
             | I think you might have just got used to it, but it's
             | definitely worse. My job involves consistent writing of 50%
             | Clojure, 40% Java, 3% Kotlin, 3% Scala and another 3% of
             | others (Python, Javascript, HTML, SQL, etc.). And so as I
             | actively code in both Lisp and Algol syntaxes (and wtv
             | Python is), I definitely always miss my structural
             | navigation and editing features in the non-Lisp languages.
             | So I'm guessing you kind of just forgot how nice it is.
             | 
             | Having said that, there's some disadvantages to the Lisp
             | syntax as well, rightward drift due to constant nesting is
             | real, and can make readability and even some edits a lot
             | more annoying, whereas the flatter structure of other
             | syntaxes doesn't have this problem as much. I still find
             | its pros outweighs the cons personally, but your mileage
             | may vary.
        
               | jb1991 wrote:
               | Nah, the benefits of structural editing can be nice, but
               | so too are the benefits of a great line-by-line editing
               | workflow ala vim, which makes one just as productive,
               | regardless of language.
               | 
               | And I think that a job where you have to constantly
               | switch between lisp and non-lisp styles would be a lot
               | more frustrating than just using only one style and
               | getting used to it, so I can see your pain there.
        
           | Igelau wrote:
           | Strike against all those "Lisp makes you a better programmer"
           | arguments?
        
             | dkersten wrote:
             | More like a strike against other languages for not having
             | the same editing tools.
        
             | p_l wrote:
             | Not really, it's not lisp's fault that you have to
             | carefully count parens in modern JavaScript without the
             | tools that make it easy in Lisp, same with nonsensical
             | indentation in Python (miss one space and watch the program
             | explode in worst possible moment).
             | 
             | What Lisp helps you is grokking actual operations of
             | computing and, especially when all you have is a really
             | dumbed down algol, opens you up to more programming methods
             | and techniques. All of that happens on layer high above
             | syntax.
        
           | city41 wrote:
           | I agree with you and I really enjoy the flow you can get into
           | with s-expressions. But in my experience this frustration is
           | typically viewed as very different and foreign from the
           | frustration more mainstream languages bring. I think one
           | reason is the mainstream frustrations have a lot of
           | similarities. Wrangling blocks in C like languages and even
           | languages like Python and Ruby have a lot of similarities,
           | where as in a lisp it's just completely different.
        
           | fiddlerwoaroof wrote:
           | Paredit (smartparens, actually) works semi-decently in almost
           | every language I've tried: there are quirks that have to be
           | worked around, but splicing/slurping/etc. are a lot more
           | convenient than the alternatives.
        
             | dkersten wrote:
             | Sure, its just more useful when your languages syntax
             | revolves around parentheses.
        
         | vikeri wrote:
         | I use paredit myself but I also think that parinfer is very
         | interesting: https://shaunlebron.github.io/parinfer/
        
           | jgalt212 wrote:
           | The repo is archived. Last commit is 2 years ago. Is the
           | project completed, or abandoned?
        
             | didibus wrote:
             | Both, most editors have their own implementation though,
             | this repo is more a demonstration on the idea around the
             | UX.
        
         | kmstout wrote:
         | ParEdit's nice, especially when combined with mark-sexpr (C-M-
         | Spc) and narrow-to-region (C-x n n). Somewhere out there is a
         | smattering of Elisp for gracefully handling recursive
         | narrowing.
        
         | dgb23 wrote:
         | The interesting thing is that Lisp syntax editing mechanics
         | scale. There are more "moves" that you can do with a Lisp,
         | especially if most your code is also functional.
         | 
         | My approach: learn 2-4 navigational moves first for a while.
         | Then add more nav and editing moves at a later point.
         | 
         | As with everything: deliberate practice leads to mastery. At
         | some point it becomes apparent that the weird syntax is a
         | feature that has huge upsides (another one being macros for
         | example).
        
       | x87678r wrote:
       | Has anyone seen a big - multi year system done in FP? Lots of
       | people love FP, it seems great for your own side project, but I'm
       | not convinced it works in those typical big corporate systems
       | where devs turn over every few years as the code base grows.
        
         | JackMorgan wrote:
         | I've worked in big F# projects in banking. It's absolutely
         | superior to C# or Java in many ways. One notable drawback is
         | "how much code can new hires write in their first month" which
         | is not a metric I consider that important for big enough
         | projects. The month or so needed to skill up a C# or Java
         | programmer in F# is a drop in the bucket compared to the
         | benefits it brought us.
        
         | adamkl wrote:
         | Nubank seems to be doing just fine:
         | 
         | "From [its] start in 2013, Nubank has grown to 600 Clojure
         | developers, running 2.5 million lines of Clojure code in 500
         | microservices"[0]
         | 
         | [0] https://www.fintechfutures.com/2020/07/brazilian-
         | challenger-...
        
           | Scarbutt wrote:
           | To be fair, they had to buy Cognitect to cope with the
           | technical debt of Clojure and Datomic.
        
         | sodapopcan wrote:
         | CircleCI is a clojure shop.
        
       | sukh wrote:
       | The article and examples were lucid and easy to read. Thank you!
        
       | BossingAround wrote:
       | How about Clojure vs Scala? Anecdotally speaking, I've seen more
       | Clojure than Scala at my company, both being incredibly niche
       | (I've seen more Groovy than either to be honest).
       | 
       | If I want to get more into FP, is there any strong
       | positives/negatives for either? I must say though that after
       | using Racket for a bit, I am a fan of the parens. Makes
       | expressions crystal clear.
        
         | dehrmann wrote:
         | I don't see a future for Scala. Since Java got lambdas and var,
         | enough of the pain is gone that Scala ends up adding its own
         | pain. And now there's Kotlin if you really want to avoid
         | boilerplate and not deal with sbt.
         | 
         | Clojure is actually designed as a functional language and not
         | as multi-paradigm as Scala.
        
         | kodon wrote:
         | Scala seems to get the most love from spark users. But even
         | then the python bindings are pretty good. Scala 3 is going to
         | be released soon, so there might be a surge of interest.
        
         | schmooser wrote:
         | Several big mainstream products are implemented in Scala,
         | including Apache Spark and Akka. Clojure has nothing of
         | comparable size.
        
           | elamje wrote:
           | Apache Storm was written in Clojure, and was only recently
           | rewritten in Java:)
        
       | darksaints wrote:
       | I always think it's hilarious when Clojure enthusiasts try to
       | address concerns about the language by talking about parentheses,
       | as if that was actually the major barrier to entry. The
       | parentheses are at best a mild inconvenience...many people love
       | them, including myself, but few people cite the parentheses as a
       | reason to not use the language after actually trying it out. A
       | non-exhaustive list on why _not_ clojure:
       | 
       | * It's slow
       | 
       | * Development with the REPL is slow because the startup times are
       | glacial and REPL-oriented development usually requires tons of
       | from-scratch restarts.
       | 
       | * The tooling sucks: build systems, IDEs, debuggers, etc. If you
       | feel like writing code with just an editor and a terminal is like
       | going back to the stone ages, you're gonna want to bash your own
       | head in with a mammoth bone club.
       | 
       | * Java interop is a black art, and when you need to use it, it
       | will ruin any sense of elegance you felt for your code
       | originally.
       | 
       | * The ecosystem practically doesn't exist, unless you're willing
       | to absorb a lot of java libraries. See above.
       | 
       | * The lack of static types hurts you in many ways, most of all
       | your ability to refactor with confidence.
       | 
       | * Clojurescript isn't the same language, no matter what they
       | promise you. Clojurescript is weakly typed, Clojure is strongly
       | typed. If you aren't aware of the difference, prepare to spend
       | weeks of your life tracking down bugs that would only be days in
       | Clojure, and would never exist in the first place in a
       | statically/strongly typed language.
        
         | EastLondonCoder wrote:
         | Here are my observations after doing clojure for a bit more
         | than a year coming from doing js for two decades.
         | 
         | It's fast, both clj and cljs but performance is non
         | deterministic and as with most functional languages it can be
         | hard to reason about performance. Profiling cljs is very hard
         | 
         | Coming from the js world I feel that tooling generally works.
         | Especially when it comes to build systems. Very happy to not
         | deal with webpack inconsistencies. With regards to ides there's
         | cursive for IntelliJ users, Calva for vscode.
         | 
         | I can't comment that much on Java interop. Js interop is a mild
         | inconvenience.
         | 
         | With regards to ecosystem, google ability is an issue but
         | tempered by the simplicity of both clj and cljs.
         | 
         | About types, there are a class of bugs they will help to avoid.
         | It does help with understanding intent of the code you work
         | with. The drawback is that they help facilitate abstraction and
         | does not help with reasoning that much about a program actually
         | runs.
         | 
         | Yes cljs is a different language. But in practice it feels very
         | much the same. I do however have a big issue with being able to
         | push deterministic performance out of it. Keeping execution
         | time for any frame below 16ms can be a challenge and for some
         | type of front end stuff js is to be preferred
         | 
         | All of these things are quite insignificant on how fast a team
         | can churn out good quality code using clojure. I've never seen
         | any team that I'm with at the moment write correct, readable
         | and fast code as quick.
        
         | dgb23 wrote:
         | Clojure is fast or similar in comparison to main stream dynamic
         | languages.
         | 
         | REPL development is what I miss so much from other languages.
         | You typically don't add dependencies as frequently for startup
         | being an issue.
        
         | codespin wrote:
         | Tooling, ecosystem, and Java are what killed clojure for me. I
         | love the language and the way Rich Hickey approaches it, but I
         | was constantly frustrated with the lack of an IDE and having to
         | stop to deal with Java and Java tooling errors all the time.
         | 
         | In the end I felt like Clojure was too clever for its own good.
         | By relying on Java (which was a great choice) it took a lot of
         | the oxygen out of the ecosystem for other devs to build tooling
         | and libraries, and without that there aren't as many people
         | participating or becoming a well known community member from
         | their work there. Again, can't say this was the wrong choice,
         | but in my opinion ecosystem is the #1 thing for a language and
         | the way Clojure was done had an impact on how the ecosystem
         | could grow.
        
         | Scarbutt wrote:
         | To expand:
         | 
         | Compared to JS, yes, it's slow. You can make Clojure run faster
         | but you will be writing Java with parenthesis not Clojure.
         | 
         | Startup times are indeed atrocious, in Clojure the REPL is
         | obligatory and not optional because of this. You can avoid REPL
         | restarts by using a third party lib like component but you have
         | to buy into a new architecture for your program that can be
         | overkill for many occasions.
         | 
         | Java interop is not a black art but it is painful, the JVM has
         | many great libs but the majority are not, many are loaded with
         | lots of methods returning void, data models like everything
         | needs to a be subclass of an abstract class, etc... So while
         | you can develop with time how to write ad-how Clojure wrappers
         | for your use case faster, you waste lots of time doing it. And
         | yes, this something you have to continually di cause as you
         | said, the ecosystem is very poor.
         | 
         | Without going into the static vs dynamic typing debate, I would
         | say Clojure is at the top of the dynamic languages spectrum
         | (the best in its class if the JVM fits nicely the problem at
         | hand).
         | 
         | Clojurescript's interop with the JS ecosystem is crippled by
         | relying on the closure compiler, which doesn't play well with
         | anything in the JS ecosystem.
        
         | dimitar wrote:
         | While everyone is entitled to their own opinion, I think it is
         | worthwhile to give more detailed examples - like for example
         | slow compared to what?
         | 
         | For example there is a nice discussion over slow startup (note
         | that for the most people the impression that it is slow comes
         | from the Leiningen startup which starts two JVM processes):
         | https://stuartsierra.com/2019/12/21/clojure-start-time-in-20...
        
           | Scarbutt wrote:
           | That article is disingenuous, the startup time of "hello
           | world" in Clojure is still pretty bad but tolerable, the
           | issue arises when you start to add libraries, now your web
           | service can take from 1-2 minutes or more to start.
        
           | darksaints wrote:
           | Okay, here is an example. Let's say you're writing a web app,
           | and you want to stick with the JVM. Pull up with Web App
           | Framework Benchmarks, narrow it down to Java, Kotlin, Scala,
           | and Clojure. Let me know when you have stopped scrolling,
           | cause it takes a while before you see the first Clojure
           | framework.
           | 
           | Unfortunately, web apps are probably the thing that Clojure
           | does best, and Clojure is already plenty slow there. Try it
           | out for something typically throughput intensive (like
           | mathematical programming or machine learning), or short lived
           | (like CLI apps), and you'll give up almost immediately.
        
             | fulafel wrote:
             | https://www.techempower.com/benchmarks/#section=data-r19&hw
             | =...
             | 
             | Clojure is on place #20 with a score of 1.3M vs 1.6M of the
             | winner. That's fast enough.
             | 
             | More broadly: These kinf of benchmark numbers matter in a
             | pretty small niche of web services and it rarely makes
             | sense to make it a big factor in PL choice.
        
               | darksaints wrote:
               | Lol, a cherry picked benchmark and it's for json using a
               | Jackson wrapper lib. That's some lovely Clojure
               | representation right there.
        
               | macintux wrote:
               | From the HN guidelines:
               | 
               | > Be kind. Don't be snarky...Please don't sneer,
               | including at the rest of the community.
        
             | capableweb wrote:
             | There haven't really been a lot of efforts to get a high-
             | speed web frameworks done in just Clojure as that's not
             | normally how professional Clojure developers work and
             | deploy code. Not a lot of Clojure developers use frameworks
             | in the first place, so the HTTP server ends up being
             | something that gets pulled in as a library, and since it's
             | running on the JVM, you use JVM servers, which are fast.
             | vertx seems to be something that scores high, but
             | unfortunately only the Scala binding seems to be in the
             | benchmark you mentioned. Here's a Clojure alternative:
             | https://github.com/vertx-clojure/vertx
             | 
             | CLI apps are easily solved with Babashka now.
        
         | jgalt212 wrote:
         | > REPL-oriented development usually requires tons of from-
         | scratch restarts.
         | 
         | I heard that. I've read Clojure developers keeping the same
         | REPL running for days and avoiding this problem, but I'd always
         | be worried about the state of the global namespace not being
         | what I thought it was. And this is especially true during
         | development / exploration.
        
           | dimitar wrote:
           | The only time I need to restart is when I load new libraries
           | but you can use https://github.com/clj-commons/pomegranate
        
       | non-e-moose wrote:
       | I'm not sure I see the benefits here unless you are buying
       | completely in to: Emacs, and Java and ignoring
       | performance/overhead. Closure is implemented in Java; and the
       | only apparent way to write it is via Emacs. The zero-eth issue I
       | see: functional programming espouses absolutely no side effects,
       | meaning no capability of handling network of physical I/O errors
       | The implementation being in Java means that it is not possible to
       | use it in embedded environments (which might not be a problem for
       | some users) but it does mean that performance is JVM limited.
       | Personally, I find the requirement of Emacs to be QUITE odious.
       | In my opinion, Emacs is an OS/environment and not an editor. I'll
       | use it if I want to edit a binary, but when an editor includes a
       | psychotherapist mode (Eliza) it is not suitable for software
       | development. I have also seen some quite talented
       | researchers/engineers spend multiple seconds trying to remember
       | the sequence to do X in Emacs, when it would have taken FAR less
       | time in vi or vim. Too many parentheses make the code
       | UNMAINTAINABLE in a production environment.
        
         | newtwilly wrote:
         | There are other IDEs people like besides emacs.
        
       | fmakunbound wrote:
       | I was using Clojure for a while before 2018, and the error
       | messages were brutal. Has that improved since then?
        
         | thom wrote:
         | They've improved over the years, but they're still terrible
         | compared to something like Rust. There are more ways to
         | sidestep the issue though, if you develop with Spec and
         | generative tests etc.
        
         | FredrikMeyer wrote:
         | Yes, they got a lot better in Clojure 1.9.
        
         | dgb23 wrote:
         | I'd say this is still the weakest part.
        
         | capableweb wrote:
         | Definitely. If you're using Clojure on the JVM, you definitly
         | should read up on Java exceptions even if the errors have
         | improved already, as it'll help you debug things, but the
         | default error messages in Clojure has improved since then. Same
         | if you're dealing with ClojureScript, you'll need to understand
         | JavaScript errors and stack traces then.
         | 
         | Probably a good starting point for people today is go straight
         | for Babashka, easy to get started and fast:
         | https://github.com/babashka/babashka
        
       | draw_down wrote:
       | I no longer think language matters much. I like some more than
       | others but they're all a bag of hurt, you just get to pick which
       | kind of hurt. I agree that pure functions are nice. But things
       | like this end up mattering a lot less day-to-day than dumb-guy
       | practical stuff like packaging and tooling. The important thing
       | about clojure is that it runs on the jvm.
        
       | tkdc926 wrote:
       | > In Clojure, whenever you "append" to a vector (array) you get a
       | "new" vector and the original does not change. Anyone with a
       | reference to the original can always count on it being the same.
       | 
       | This has never made any sense to me. Can someone please explain
       | why you would still want the original vector to continue to exist
       | with data that no longer reflects the current system? What am I
       | missing?
        
         | tooltower wrote:
         | To avoid race conditions and to preserve abstraction
         | boundaries. This is the archetypical design pattern in
         | functional languages. For a simple example, if you first check
         | the size of a vector before accessing index `i`, you can be
         | sure the length hasn't changed right after your size-check.
         | It's exactly like writing code only using only immutable
         | Strings of Java, or frozen sets of Python.
         | 
         | So in your example, it "continues to exist" in local variables
         | to reflect the state of the system as it was when you read it,
         | as long as you still hold a reference to the old vector.
         | Typically, you'd ask your software to fetch a fresh copy of the
         | vector any time you'd want new data. But that's explicit in
         | code. You'll have fewer surprises if mutation (like vector-
         | appends) are never shared between variables.
        
         | jsiepkes wrote:
         | Immutability is very useful for dealing with concurrency. For
         | example if a thread is iterating over a vector and an other
         | thread mutates it you don't want the first thread to get "the
         | rug pulled from under it" so to say. If things can never be
         | suddenly changed, you don't have to plan for that.
        
         | fbn79 wrote:
         | See this answer
         | https://stackoverflow.com/questions/34385243/why-is-immutabi...
        
           | klelatti wrote:
           | Also when no concurrency:
           | 
           | https://softwareengineering.stackexchange.com/questions/2509.
           | ..
        
         | afandian wrote:
         | A specific example:
         | 
         | I have context scratch-pad hashmap object I pass into a top-
         | level function. It can then be decorated with extra scratchpad
         | data all the way down the call-stack and passed into lower
         | functions, for them to make use of. So each function can pass
         | stuff down, but it's not available further up the call stack.
         | It effectively looks like a stack object in terms of its
         | semantics: as you unwind the stack you unwind history,
         | 'undoing' changes. And the stack can take many different paths
         | over execution.
         | 
         | Functions can do pretty much anything they want to the object
         | further down the stack, without affecting other functions'
         | inputs (parents or siblings). If it were mutable, the functions
         | would suddenly be coupled to each other, and could change each
         | other's data inputs. Add concurrency to that and it gets worse.
         | 
         | There are other ways to do this with Clojure. But I like this
         | method, it's obvious and easy to test. It also feels
         | reminiscent of Prolog.
         | 
         | In my example I'm associating new values into a hashmap, not
         | appending to a vector, but it amounts to the same thing.
        
         | mac01021 wrote:
         | Suppose I provide you with a black-box function foo that takes
         | an int.
         | 
         | You can write the program                  x =
         | read_int_from_terminal();        y = foo(x);        println(x +
         | ":  " + y);
         | 
         | And you can be confident that invoking foo has no effect on the
         | value of x that will print out on subsequent lines. x is a
         | local variable that refers to a stateless, immutable,
         | mathematical object. If x refers to the number 3, it will
         | continue to do so until you personally tell it to refer to
         | something else.
         | 
         | In clojure, as in other functional programming environments, a
         | vector is also a stateless, immutable, mathematical entity.
         | Which is nice because nobody can change its state out from
         | under you and that makes programs easier to reason about.
         | 
         | There are also specific use cases where this feature may shine
         | in a specific way, for example making it easy to maintain an
         | "undo history" when implementing a text editor. If the state of
         | a buffer in your editor is an immutable value then it's easy to
         | maintain a stack or list (or whatever) of all the states of the
         | buffer - the top of the stack being the current state - and
         | operations on the buffer simply create a new version but do not
         | destroy any information about prior versions.
         | 
         | Outside of such specific use cases, though, it's just about
         | referential transparency and enhanced ability to reason about
         | the interactions between different pieces of code.
        
         | jb1991 wrote:
         | The main thing you're missing is the (real) functional
         | programming style, which expects this kind of behavior when
         | manipulating data. To refer to it as a reference is somewhat
         | misleading, functional programs are just dealing with a raw
         | block of data, which is transformed into a new block of data.
         | But to call it the old data and the new data is kind of silly,
         | because it's not really about state at all.
        
       ___________________________________________________________________
       (page generated 2021-01-03 23:00 UTC)