[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)