[HN Gopher] Six Years of Professional Clojure
       ___________________________________________________________________
        
       Six Years of Professional Clojure
        
       Author : erez-rabih
       Score  : 183 points
       Date   : 2021-08-02 11:52 UTC (11 hours ago)
        
 (HTM) web link (engineering.nanit.com)
 (TXT) w3m dump (engineering.nanit.com)
        
       | EdwardDiego wrote:
       | > An incoming HTTP request? it is a plain Clojure dictionary.
       | 
       | I learned to code in Python. Loved it. Dynamically typed dicts up
       | the wazoo!
       | 
       | Then I learned why I prefer actual types. Because then when I
       | read code, I don't have to read the code that populates the dicts
       | to understand what fields exist.
        
         | [deleted]
        
         | robertlagrant wrote:
         | I agree. This doesn't seem much different to saying they're all
         | objects. You still need to know what to expect inside the
         | dictionary.
        
           | goatlover wrote:
           | The difference being that objects have a class where you can
           | look to see what fields it specifies.
        
             | robertlagrant wrote:
             | Sure, depending on the language. What I mean is having
             | dictionaries doesn't mean you don't have to learn schemas.
        
             | dan-robertson wrote:
             | Java doesn't really have a nice interface for interacting
             | with objects in general. Closure _does_ have a nice
             | interface for interacting with dictionaries. They have
             | namespaces keyword symbols for keys which are much more
             | ergonomic than typing strings, and they have lots of
             | functions for modifying dictionaries. I think the big
             | difference is in the philosophy of what the language thinks
             | data is, and how the world ought to be modelled.
        
         | lvh wrote:
         | The two are not mutually exclusive. Clojure has namespaced
         | keywords and specs[0] to cover that. (There is also the third-
         | party malli, which takes a slightly different appproach.)
         | 
         | The advantage is that maps are extensible. So, you can have
         | middleware that e.g. checks authentication and authorization,
         | adds keys to the map, that later code can check it directly.
         | Namespacing guarantees nobody stomps on anyone else's feet.
         | Spec/malli and friends tell you what to expect at those keys.
         | You can sort of do the same thing in some other programming
         | languages, but generally you're missing one of 1) typechecking
         | 2) namespacing 3) convenience.
         | 
         | [0]: spec-ulation keynote from a few years ago does a good job
         | explaining the tradeoffs;
         | https://www.youtube.com/watch?v=oyLBGkS5ICk
        
         | kitd wrote:
         | Yeah, he mentions that later on as a drawback
        
         | dan-robertson wrote:
         | Question: 1. Can a GET request have a non-empty request body?
         | 
         | 2. Assuming you don't know the answer to that question, will
         | the type system you use be able to tell you the answer to that
         | question?
         | 
         | This is a pretty simple constraint one might want (a constraint
         | that only certain requests have a body) but already a lot of
         | static type systems (e.g. the C type system) cannot express and
         | check it. If you can express that constraint, is it still easy
         | to have a single function to inspect headers on any request?
         | What about changing that constraint in the type system when you
         | reread the spec? Is it easy?
         | 
         | The point isn't that type systems are pointless but that they
         | are different and one should focus on what the type system can
         | do for you, and at what cost.
        
           | lkitching wrote:
           | Any statically-typed language with generics can express that
           | by parameterising the request type with the body type. A
           | bodiless request is then just Request[Nothing] (or
           | Request[Unit] if your type system doesn't have a bottom
           | type). Accessing the headers just requires an interface which
           | all static languages should be able to express.
        
             | dan-robertson wrote:
             | (1) note that "statically-typed language with generics"
             | excludes a lot of statically typed languages, including C
             | and Go (at least pre generics).
             | 
             | (2) this misses the meat of the question which is how to
             | express that (eg) a _GET_ request doesn't come with a body
             | and a _POST_ request does. I suppose that you're suggesting
             | that one registers a url handler with a method type and
             | that forces the handler to accept responses of a certain
             | type. Or perhaps you are implicitly allowing for sun types
             | (which aren't a thing in many static type systems.)
             | 
             | (3) even in C++, isn't this suggestion hard to work with.
             | That is, isn't it annoying to write a program which works
             | for any request whether or not it has a body because the
             | type of the body must be a template parameter that adds
             | templates to the type of every method which is generic to
             | it. But maybe that is ok or I just don't understand C++.
        
             | xapata wrote:
             | How about values restricted to identifiers currently in the
             | database table? There's always something the type system
             | can't do.
        
               | [deleted]
        
               | jolux wrote:
               | F# has a feature called type providers that make this
               | sort of bookkeeping between the database and the code
               | less tedious, but even if you mess it up, static typing
               | still gives you more safety than dynamic. If your code
               | blew up because it should have accepted an identifier it
               | didn't, you know that the code has not been written to
               | handle that case and can fix it. Alternatively, you can
               | just choose to ignore this, and do what a dynamic
               | language does. There is nothing stopping you from being
               | dynamic in a static language, passing everything around
               | as a map, etc.
        
               | dharmaturtle wrote:
               | A demo of a SQL type provider in action:
               | https://youtu.be/RK3IGYNZDPA?t=2539
               | 
               | It requires a bit of elbow grease to make it work with a
               | CICD system... but it works :D
        
           | twic wrote:
           | 1. Yes. It's weird, but it's legal HTTP.
           | 
           | 2. Sure. The request type has a body property.
        
             | dan-robertson wrote:
             | Does "the request type has a body property" actually imply
             | (1) though? In a language like C or C++ or Java, you could
             | have a protocol like "body is always null on GET requests."
             | The question isn't really about HTTP, that was just an
             | easy-to-reach-for example, it is really about what having
             | explicit types allows one to deduce about a program.
        
         | taeric wrote:
         | To be fair, an incoming request is, almost by definition,
         | dynamic. It makes sense to have that as a map, since the main
         | sensible thing to do on receipt is validation/inspection.
         | 
         | Granted, you may have a framework do a fair bit of that.
         | Depends how much you want between receipt of the request and
         | code you directly control.
        
           | Zababa wrote:
           | Usually the approach in a statically-typed language is to
           | transform your dynamic request into something that you know
           | through parsing instead of validation. Here's a great article
           | about this: https://lexi-
           | lambda.github.io/blog/2019/11/05/parse-don-t-va....
        
             | taeric wrote:
             | That is a valid approach in any language. Static or not.
             | Doesn't change my point that heavily. And it is all too
             | possible to pick a bad parsing/binding language such that
             | protocol changes in the request are now foot guns.
        
               | Zababa wrote:
               | That's true, but static languages are not worse at
               | handling dynamic data. From the same author:
               | https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-
               | typ....
        
               | taeric wrote:
               | To an extent, I agree. I'm just pointing out that this is
               | a bit of a bad example. I want there to be dynamic
               | inspection of input.
               | 
               | That said, maps as the only tool is clearly messy. And is
               | a straw man.
        
             | kingdomcome50 wrote:
             | This is the second time I've seen the link above. And while
             | I agree with the premise, the author _clearly_ does not
             | understand how to properly use the `Maybe` monad (a term
             | that does not make an appearance!).
             | 
             | There is little use in wrapping a call in `Maybe` to then
             | _immediately_ unwrap the result on the next line. Doing so
             | isn 't really using the construct... One would expect the
             | lines following the creation of `Maybe` to _bind_ calls
             | through the monad.
             | 
             | In the end I see almost no meaningful difference between
             | their "Paying it forward" example and simply utilizing an
             | `if` to check the result and throw. In essence the author
             | is using a parse _and_ validate approach!
        
               | travv0 wrote:
               | Lexi _absolutely_ understands how to properly use the
               | Maybe monad. What you 're saying to do here is the exact
               | opposite of what this post is advocating for. You're
               | talking about pushing the handling of the Maybe till
               | later and the post is all about the advantages of
               | handling it upfront and not having to worry about it
               | anymore. You might want to read it one more time.
        
               | kingdomcome50 wrote:
               | I understand. But what is purpose of `Maybe`? The reason
               | one would reach to the above construct is _precisely_ to
               | offload (pushing to later) the handling of a value that
               | may (or may not) be present at runtime such that a
               | developer can write code assuming the value is always
               | present and ignore the `Nothing` case.
               | 
               | Sure you can unwrap it right away, but that isn't
               | necessary because you could _also_ just  "bind" the next
               | function call to the monad (which is more idiomatic to
               | the construct). You _never_ have to worry about that
               | value in this case because... well... that 's the benefit
               | of using `Maybe`.
               | 
               | I'm not super familiar with Haskell, but my sense is that
               | the author is trying more to please the compiler ( _at a
               | specific point in the program!_ ) than simplify the
               | logic. That is, they want a concrete value (`configDirs`)
               | to exist in the body of `main` more than they want the
               | cleanest representation of the problem in code.
        
               | travv0 wrote:
               | > But what is purpose of `Maybe`?
               | 
               | In this case, it's to provide a better error message in
               | case there's an empty list than `fromList` would provide.
               | 
               | > You never have to worry about that value in this case
               | because... well... that's the benefit of using `Maybe`.
               | 
               | But you do, your entire program doesn't live in `Maybe`
               | so at some point you have to check whether it's `Just a`
               | or `Nothing`. Once again, the whole point of the post is
               | to argue that getting out of the `Maybe` as close to
               | parsing time as possible is preferable so you have a more
               | specific type to work with after that. You also see right
               | away what didn't parse instead of just knowing that
               | _something_ didn 't parse, which is what would happen if
               | you stayed in the `Maybe` monad for all your parsing.
        
               | kingdomcome50 wrote:
               | > your entire program doesn't live in `Maybe`
               | 
               | Well... if your entire program is dependent on some input
               | that may or may not exist at runtime... then it kind of
               | _does_ live in `Maybe`.
               | 
               | I have no issue with unwrapping a `Maybe` to throw an
               | exception. But I _do_ find it a bit ironic that the post
               | is about parsing instead of validating, that the perfect
               | construct is _right there_ to exemplify how it could be
               | done, but the author then chooses to eschew it and
               | instead show examples of how validation could look.
               | 
               | The body of `main`, for example, could be refactored to
               | something like:                   maybeInitialized <-
               | (getConfigurationDirectories >>= head >> initializeCache)
               | 
               | Which actually _shows_ how `Maybe` can be used to
               | simplify the system. If you want to unwrap the maybe at
               | this point to throw, go for it! But the above is a _much_
               | cleaner representation of the program than what author is
               | trying to do (it 's crystal clear how the cache _might_
               | get initialized). I would expect  "Parse don't validate"
               | to be about how useful `Maybe` is to combine parsing
               | logic into a functional flow vs. how validation leads to
               | an ugly procedural approach.
        
               | garethrowlands wrote:
               | I think you're referring to this part of the
               | `getConfigurationDirectories` action, which has type `IO
               | (NonEmpty FilePath)`:                   case nonEmpty
               | configDirsList of           Just nonEmptyConfigDirsList
               | -> pure nonEmptyConfigDirsList           Nothing ->
               | throwIO $ userError "CONFIG_DIRS cannot be empty"
               | 
               | The "meaningful difference" you're looking for is the
               | type of `getConfigurationDirectories`. The previous
               | version had type `IO [FilePath] `, which _doesn't_
               | guarantee any configuration directories at all. It did
               | indeed check the results and throw. But it doesn't
               | guarantee that all the `[FilePath]` values in the program
               | have been checked. There are neither tests nor proofs in
               | this code. In contrast, with the revised version, you can
               | be certain anywhere you see a `NonEmpty FilePath` it is
               | indeed non-empty.
               | 
               | The code I've quoted that checks which case we have, is
               | the only place that needs to handle that `Maybe`. Or
               | maybe `main`, if we want to be more graceful. The author
               | (I wouldn't say I know her but I know that much) does
               | know how to chain maybes with bind but it's not necessary
               | in this example code.
        
               | kingdomcome50 wrote:
               | My point is that if you are not chaining `Maybe` then the
               | utility of employing the construct is unobserved. The
               | entire _purpose_ of using `Maybe` is to relieve the
               | client from the need to make checks at every call for a
               | value that may (or may not) exist. If you intend to
               | immediately  "break out" of the monad and (even more
               | specifically) throw an error, you might as well just use
               | an `if`.
               | 
               | I'm sure `main` _could_ be written to  "bind"/"map"
               | `getConfigurationDirectories` with `nonEmpty`, `head`,
               | and `initializeCache` in a way that puts the `throw` at
               | the top-level (of course the above implementations may
               | need to change as well). Unfortunately I'm not familiar
               | enough with Haskell to illustrate it myself.
        
               | lkitching wrote:
               | The purpose of Maybe is to explicitly represent the
               | possible non-existence of a value which in Haskell is the
               | only option since there's no null value which inhabits
               | every type. The existence of the monad instance is
               | convenient but it's not fundamental. The type of
               | getConfigurationDirectories could be changed to MaybeT IO
               | (NonEmpty FilePath) to avoid the match but I don't think
               | it would make such a small example clearer.
        
               | kingdomcome50 wrote:
               | There are numerous ways to redesign the function
               | signatures, but I would imagine the simplest would be
               | (again, idk Haskell syntax):
               | getConfigurationDirectories: unit -> Maybe [FilePath]
               | nonEmpty: [a] -> Maybe [a]         head: [a] -> Maybe a
               | initializeCache: FilePath -> unit
               | 
               | Notice `nonEmpty` isn't really necessary because `head`
               | could to the work. The above could be chained into a
               | single, cohesive stack of calls where the result of each
               | is piped through the appropriate `Maybe` method into the
               | next call in a point-free style. I cannot imagine how
               | this wouldn't be clearer. e.g:
               | maybeInitialized <- (getCofigurationDirectories >>= head
               | >> initializeCache)
               | 
               | That's the whole thing. Crystal clear. The big takeaway
               | of "Parse don't validate" should be about the predominant
               | use of the `Maybe` monad as a construct to make "parsing"
               | as ergonomic as possible! Each function that returns
               | `Maybe` can be understood as a "parser" that, of course,
               | can be elegantly combined to achieve your result.
               | 
               | My critique is exactly that unwrapping the `Maybe`
               | immediately in order to throw an exception is kind of the
               | worst of both worlds. I mentioned this in a sibling
               | comment, but my sense is that the author is more
               | concerned with have a concrete value (`configDirs`)
               | available in the scope of `main` than best-representing
               | the solution to the problem in code. It is a shame
               | because I _agree_ with the thesis.
        
               | lkitching wrote:
               | On the contrary the The NonEmpty type is fundamental to
               | the approach in that example since it contains in the
               | type the property being checked dynamically (that the
               | list is non-empty). The nonEmpty function is a simple
               | example of the 'parse don't validate' approach since it
               | goes from a broader to a more restricted type, along with
               | the possibility of failure if the constraint was not
               | satisfied. The restriction on the NonEmpty type is what
               | allows NonEmpty.head to return an a instead of a (Maybe
               | a) and thus avoid the redundant check in the second
               | example. The nonEmpty in your alternative implementation
               | is only validating not parsing since after checking the
               | input list is non-empty, it immediately discards the
               | information in the return type. This forces the user to
               | deal with a Nothing result from head that can never
               | happen. Attempting to clean the code up by propagating
               | Nothing values using bind is just hiding the problem that
               | the validating approach avoids entirely.
        
               | dharmaturtle wrote:
               | You might try re-reading it with some charity - the
               | example's purpose isn't to teach the `Maybe` monad, but
               | to remove the redundant check. To go into what `bind`
               | does would be a diversion from the main topic (parsing vs
               | validating).
               | 
               | FWIW SPJ has called this blog's author a "genius" so... I
               | think they do know how `Maybe` works. https://gitlab.hask
               | ell.org/ghc/ghc/-/issues/18044#note_26617...
        
               | kingdomcome50 wrote:
               | But `Maybe` is specifically designed to remove redundant
               | checks for a value that may (or may not) be present!
               | That's the whole point of the monad! It seems rather
               | unfortunate this isn't highlighted (or at least
               | illustrated) doesn't it?
               | 
               | I generally _agree_ with the premise of the post.
        
         | fmakunbound wrote:
         | This is one of those self-inflicted Clojure problems. In Common
         | Lisp you might use an alist or a plist for small things, but
         | you'd definitely reach for CLOS classes for things that had
         | relationships to other things and things that had greater
         | complexity.
         | 
         | IIRC, the preference for complecting things via maps, and then
         | beating back the hordes of problems with that via
         | clojure.spec.alpha (alpha2?) is a Hickey preference. I don't
         | recall exactly why.
        
           | blacktriangle wrote:
           | No source to back this up, but my guess is that Clojure was
           | driven by the need to interopt with Java so is to not get
           | kicked out of production. This meant absorbing the Java
           | object model. Shipping a language with both Java objects and
           | CLOS and making them both play nice together sounds like a
           | nightmare.
        
           | joncampbelldev wrote:
           | This comment helpfully explains many of the reasons Rich had
           | for choosing immutable, persistent, generic data structures
           | as the core information model in clojure (instead of concrete
           | objects / classes):
           | https://news.ycombinator.com/item?id=28041219
           | 
           | Not wanting to misquote the above / Rich himself I would TLDR
           | it to:
           | 
           | - flexibility of data manipulation
           | 
           | - resilience in the face of a changing outside world
           | 
           | - ease of handling partial data or a changing subset of data
           | as it flows through your program
           | 
           | Please note that no one (I hope) is saying that the above
           | things are impossible or even necessarily difficult with
           | static typing / OOP. However myself and other clojurists at
           | least find the tradeoff of dynamic typing + generic maps in
           | clojure to be a net positive especially when doing
           | information heavy programming (e.g. most business
           | applications)
        
         | tragomaskhalos wrote:
         | Namedtuples FTW! A de-facto immutable dict with the keys listed
         | right there in the definition to obviate all the usage head-
         | scratching. Then, if you need more functionality (eg factory
         | functions to fill in sensible defaults), you can just subclass
         | it.
         | 
         | TBH I've never understood the attraction of the untyped dict
         | beyond simple one-off hackups (and even there namedtuples are
         | preferable), because like you say you typically have no idea
         | what's supposed to be in there.
        
       | lmilcin wrote:
       | > Pure functions make code design easier: In fact, there's very
       | little design to be done when your codebase consists mostly of
       | pure functions.
       | 
       | Ummm... I am a little bit fearful about your codebase.
       | 
       | If you don't see the need for designing your FP system it
       | probably mostly means it is being designed ad hoc rather than
       | explicitly.
       | 
       | If you are trying to compare to OOP system done right, you will
       | notice that this includes a lot of work in identifying domain
       | model of your problem, discovering names for various things your
       | application operates on, and so on. Just because you elect to not
       | do all of this doesn't mean the problem vanishes, it most likely
       | is just shifted to some form of technical debt.
       | 
       | > Clojure is a dynamic language which has its advantages but not
       | once I stumbled upon a function that received a dictionary
       | argument and I found myself spending a lot of time to find out
       | what keys it holds.
       | 
       | Dynamic typing is a tradeoff which you have to be very keenly
       | aware of if you want to design a non-trivial system in a
       | dynamically typed language.
       | 
       | It is not a problem with Clojure, it is just a property of all
       | dynamically-typed languages.
        
       | dmitriid wrote:
       | One thing I don't like about all articles on clojure is that
       | basically all of them say: ah, it's just like lisp with lists
       | `(an (example of) (a list))` with vectors `[1 2 3]` thrown in. So
       | easy!
       | 
       | But then you get to Clojure proper, and you run into additional
       | syntax that either convention or functions/macros that look like
       | additional syntax.
       | 
       | Ok, granted, -> and ->> are easy to reason about (though they
       | look like additional syntax).
       | 
       | But then there's entirely ungooglable ^ that I see in code from
       | time to time. Or the convention (?) that call methods on Java
       | code (?) with a `.-`
       | 
       | Or atoms defined with @ and dereferenced with *
       | 
       | Or the { :key value } structure
       | 
       | There's way more syntax (or things that can be perceived as
       | syntax, especially to beginners) in Clojure than the articles
       | pretend there is.                   (defn ^:export db_with [db
       | entities]           (d/db-with db (entities->clj entities)))
       | (defn entity-db           "Returns a db that entity was created
       | from."           [^Entity entity]           {:pre [(de/entity?
       | entity)]}           (.-db entity))              (defn ^:after-
       | load ^:export refresh []           (let [mount
       | (js/document.querySelector ".mount")                 comp  (if
       | (editor.debug/debug?)                         (editor.debug/ui
       | editor)                         (do
       | (when (nil? @*post)                             (reset! *post (->
       | (.getAttribute mount "data") (edn/read-string))))
       | (editor *post)))]             (rum/mount comp mount)))
        
         | ronnier wrote:
         | Single engineers will pick clojure at companies , build a
         | project in it, later that engineer will move on, now nobody can
         | maintain this code so it's rewritten in some normal language.
         | I've seen that happen a few times. That code is hard to read
         | and understand. This is why clojure will remain niche.
        
           | achikin wrote:
           | It could have been Go and Java programmer trying to
           | understand it. Or it could have been some clumsy tool written
           | in node which Go programmer finds hard to read and
           | understand. Clojure's main advantage is that you can you can
           | learn it very very quickly up to the point when you
           | understand most of the code, the language is very very small
           | compared to "five main languages".
        
           | outworlder wrote:
           | > Single engineers will pick clojure at companies , build a
           | project in it, later that engineer will move on, now nobody
           | can maintain this code so it's rewritten in some normal
           | language
           | 
           | "Normal language"?
           | 
           | You mean, whatever language is most popular at the company.
           | What's "normal" at one would be completely alien at another.
           | Even things like Java. If you don't have anything in the Java
           | ecosystem, the oddball Java app will be alien and will likely
           | get rewritten into something else.
           | 
           | The reason Clojure remains niche is that some people somehow
           | think it's not a "normal" language, for whatever reason.
        
           | taeric wrote:
           | That is possible with all languages. I've seen java, scala,
           | clojure, perl, python, etc.
           | 
           | Usually this is made worse by bespoke build tools and
           | optimizations that make the system punishing to pick up.
        
             | sramsay wrote:
             | You've seen a case where someone wrote something in Python
             | that later devs could not understand and then rewrote it in
             | . . . what? And you've seen that with Java?
             | 
             | There's a big difference between a developer going off and
             | writing something in one of the top five most used
             | languages in the world and doing so in Scala.
        
               | taeric wrote:
               | Yes. I've seen and contributed to dumpster fires in all
               | of those languages. I would love to say it was all some
               | rogue developer that crapped on things, but it is often
               | just new developers. The more, the more damage.
        
               | lostcolony wrote:
               | Both are strange and alien to Javascript developers, who
               | can be full stack.
               | 
               | Python may seem simple once you know it, but going in
               | blind there's plenty of traps to bite you. Significant
               | whitespace for one.
        
               | chrsig wrote:
               | I think there's two different issues:
               | 
               | 1. picking a language/tool that a company doesn't have
               | personnel with experience using it
               | 
               | 2. picking a language/tool that is esoteric, which
               | generally implies #1 as well.
               | 
               | #1 on its own isn't great, but generally when sticking in
               | the java/python/ruby/javascript/php/etc...mainstream
               | languages, there's a lot more documentation, and there's
               | a higher chance that _someone_ in the company will have
               | some familiarity. If nothing else, it'd be easier to hire
               | a replacement for.
        
               | lostcolony wrote:
               | A higher chance, yes, but it doesn't matter much; what is
               | tricky with most applications is the domain. Certainly,
               | it's faster to go learn a language than to learn a new
               | domain. To that end, you can get the whole team trained
               | faster in a language than you can hire someone with
               | experience and train them to the domain.
        
               | joelbluminator wrote:
               | > Certainly, it's faster to go learn a language than to
               | learn a new domain.
               | 
               | It's not only the language but the framework. For example
               | I know javascript well enough but I now am quite a noob
               | with Ember in my new role. I would say the framework is
               | just as important as the language, at least when doing
               | web development.
        
               | chrsig wrote:
               | You're kind of reinforcing the point though -- now you've
               | got a whole team distracted by picking up a new
               | language....why? how is it a good use of anyone's time?
               | And it'll be a perennial training issue in the case of an
               | esoteric language, because those team members will
               | eventually turn over as well, meaning that you don't get
               | to avoid either hiring or training a new person on it.
               | 
               | If it's just one component, implemented by a single dev,
               | it really can make more sense to understand what it does
               | and rewrite it in a language that's common in the
               | company.
        
               | lostcolony wrote:
               | I'm not advocating NOT rewriting it. I'm just saying,
               | back to the great grandparent's point, that the issue is
               | a dev went rogue, NOT the language the rogue dev chose.
               | The difficulty is the same regardless of the language the
               | rogue dev chose; it's not that they picked Clojure, it's
               | that they picked a language there was no organizational
               | adoption of.
        
           | agumonkey wrote:
           | is it really hard to read (could be) or is it just that the
           | average coder never saw lisp or sml and doesn't want to
           | bother bearing the responsibility to learn something alien on
           | duty ?
        
           | mollusk wrote:
           | You need a team that wants to use Clojure. I wrote Clojure
           | professionally for 2 years, and everyone at the company was
           | excited about it and sold on the language. Even after 3-5
           | years of programming in it. Now, at a different place, we
           | write in a different language, and even though I still love
           | Clojure, I'm not gonna write some project in it, even if
           | Clojure might suit it so well, because I know these people
           | are sold on different language, and I'm not going to preach
           | and I'm not going to make their lives more difficult by
           | having to maintain some obscure codebase.
        
         | lvh wrote:
         | Minor point of order about the atoms: they're not defined with
         | @ nor derefd with _. If you 're referring to _earmuffs* that's
         | convention not syntax (specifically for dynamically scoped
         | variables, which could be atoms or anything else), and @ is
         | indeed deref. (More specifically @x is a reader macro ish that
         | expands to literally `(deref x)`.)
        
           | dmitriid wrote:
           | Thank you! I never seem to remember this (but I don't use
           | Clojure, so it's not an ingrained knowledge)
        
         | girishso wrote:
         | Agreed. These days I'm really fascinated by clojure and trying
         | to learn clojure. Other than the project setup and repl and the
         | editor (which I had considered), these weird characters are
         | throwing me off.
         | 
         | What clojure really needs is some kind of opinionated framework
         | or starter template, something like create-react-app. That has
         | all these things figured out so a beginner like me can start
         | playing with actual clojure, which documents all the steps to
         | setup the repl and editor and what not. The last time I asked
         | for this I was told about lein templates, they help but there's
         | no documentation to go with those.
         | 
         | There needs to be some push from the top level. create-react-
         | app was produced by facebook. Elm reactor (which lets you just
         | create a .elm file and play with elm) was created by Evan the
         | language creator himself.
         | 
         | tldr: There's a huge barrier to start playing with clojure that
         | needs to come down and the push needs happen from the top
         | level.
        
           | uDontKnowMe wrote:
           | There is the widely used Luminus framework
           | https://luminusweb.com/
        
             | girishso wrote:
             | Yes, of course and I've got the book as well. The problem
             | with the book is I got stuck on the _very first_ code
             | example in the book. I know there 's a forum for the book
             | where ( _hopefully_ ) I can get my query answered.
             | 
             | My point is: these are all individual attempts (the book i
             | mean) and there will always be something on page xyz broken
             | and it can't be solved by individuals. To solve these
             | problems, there needs to be constant time and money
             | investment from someone serious (like facebook in case of
             | create-elm-app).
        
               | uDontKnowMe wrote:
               | Yes I agree there is a problem of a lack of institutional
               | funding in the Clojure world. Luminus is a great tool but
               | it is a bit sad that it is arguably the most production-
               | ready web toolkit in the ecosystem and it is mostly the
               | work of a single person.
               | 
               | There is some community effort to better fund the core
               | infrastructure in Clojure through
               | https://www.clojuriststogether.org/, hopefully they can
               | continue to attract more funding developers and
               | companies.
               | 
               | In general a lot of these issues could be alleviated if
               | the community was just in general larger with more
               | contributors. I think the Clojure community is quite
               | welcoming to newbies in the sense that people are quite
               | responsive, kind and helpful around the internet, in
               | Clojurians Slack (try asking there btw, if you haven't
               | yet and are still stuck at the start of the book), etc.
               | But in other ways people seem averse to criticism or
               | suggestions from outsiders. I think the Clojure world
               | needs to do a bit of self reflection to understand why
               | adoption is so low right now and honestly consider what
               | needs to change to attract more developers and
               | contributors.
        
         | cr__ wrote:
         | Not sure how you're supposed to find this page, but it's pretty
         | useful: https://clojure.org/guides/weird_characters
        
           | dmitriid wrote:
           | Nice! I missed it (or it didn't exist) when I last looked at
           | Clojure a few years back
        
             | roenxi wrote:
             | You missed it, it has been there forever. But it says good
             | and bad things about Clojure that its reference
             | documentation is one of its weakest points.
             | 
             | The Guide/Reference split obscures a lot of information (do
             | I want guidance on Deps & CLI or do I want reference on
             | Deps & CLI?) and the guides where that gem is hidden
             | randomly mix advanced topics (eg, how to set up generative
             | testing), beginner topics (how to read Clojure code) and
             | library author topics (eg, Reader Conditionals).
             | 
             | When you think about it, there is nearly no trigger to look
             | at the guides when the information you need is there.
             | Clojure is a weird mix of both well documented and terribly
             | documented. All the facts are on the website, very few of
             | them are accessible when required. The people who make it
             | past that gauntlet are rewarded by getting to use Clojure.
        
               | dmitriid wrote:
               | In my case it was even worse, as I started with
               | ClojureScript, and official documentation was simply
               | abysmal then.
        
         | hcarvalhoalves wrote:
         | {:pre [(de/entity? entity)]}
         | 
         | is "syntactic sugar" for                   (hash-map (keyword
         | "pre") (vector (de/entity? entity)))
         | 
         | while                   (.getAttribute mount "data")
         | 
         | is calling the method `.getAttribute` on the `mount` object -
         | since it's a Lisp, it's in prefix notation. It also highlights
         | how methods are not special and just functions that receive the
         | object as first argument.
         | 
         | Finally,                   @*post
         | 
         | is the same as                   (deref *post)
         | 
         | and the `*` means nothing to the language - any character is
         | valid on symbol names, the author just chose an asterisk.
         | 
         | Most of what you believe to be syntax are convenience "reader
         | macros" (https://clojure.org/reference/reader), and you can
         | extend with your own. You can write the same code without any
         | of it, but then you'll have more "redundant" parenthesis.
        
           | dmitriid wrote:
           | > Most of what you believe to be syntax are convenience
           | "reader macros"
           | 
           | And yet, you need to know what all those ASCII symbols mean,
           | where they are used, and they are indistinguishable from
           | syntax.
           | 
           | Moreover, even Clojure documentation calls them syntax. A
           | sibling comment provided a wonderful link:
           | https://clojure.org/guides/weird_characters
        
       | MeteorMarc wrote:
       | What build tools do you use, maven?
        
         | finalfantasia wrote:
         | Clojure developers tend to choose the official Clojure CLI
         | tools [1] for new projects these days.
         | 
         | [1] https://clojure.org/guides/deps_and_cli
        
         | tribaal wrote:
         | Not the author, but most clojure projects use leiningen to
         | build and distribute projects (https://leiningen.org/)
         | 
         | This seems to be the case for the author's open-source work
         | (https://github.com/nanit/kubernetes-custom-
         | hpa/blob/master/a...)
        
       | evanspa wrote:
       | Great article, love Clojure. Was trying to figure out what Nanit
       | does. Might want to consider putting a link to the Nanit homepage
       | on your engineering page. When just typed in nanit.com and saw
       | the baby monitor tech, I thought maybe I went to the wrong place,
       | until I saw the logos matched. Anyway, good read, but please put
       | a link to your home page on your engineering site, or, put a 1
       | liner in the opening of your blog giving context to what your
       | company does.
        
       | altrunox wrote:
       | Great article, love Clojure, unfortunately couldn't find any work
       | with it when I tried, I managed to flop in the only interview I
       | got :( Still, I miss it sometimes when I'm writing C#.
        
       | cgopalan wrote:
       | Another good report about what Clojure does well is this article
       | by metabase: https://medium.com/@metabase/why-we-picked-
       | clojure-448bf759d...
       | 
       | I have had the pleasure of contributing to their code since we
       | used their product at a previous company I worked at, and I must
       | say I am sold on Clojure. Definitely a great language to have in
       | your toolbox.
        
       | dgb23 wrote:
       | I found one of the perceived weaknesses of Clojure (in this
       | article), it being dynamically typed, is a tradeoff rather than a
       | pure negative. But it applies that tradeoff differently than
       | dynamic languages I know otherwise and that difference is
       | qualitative: It enables a truly interactive way of development
       | that keeps your mind in the code, while it is running. This is
       | why people get addicted to Lisp, Smalltalk and similar languages.
       | 
       | > To understand a program you must become both the machine and
       | the program.
       | 
       | - Epigrams in Programming, Alan Perlis
       | 
       | Two of the big advantages of (gradually-) typed languages are
       | communication (documentation) and robustness. These can be gained
       | back with clojure spec and other fantastic libraries like schema
       | and malli. What you get here goes way beyond what a strict,
       | static type systems gets you, such as arbitrary predicate
       | validation, freely composable schemas, automated instrumentation
       | and property testing. You simply do not have that in a static
       | world. These are old ideas and I think one of the most notable
       | ones would be Eiffel with it's Design by Contract method, where
       | you communicate pre-/post-conditions and invariants clearly. It
       | speaks to the power of Clojure (and Lisp in general) that those
       | are just libraries, not external tools or compiler extensions.
        
         | hota_mazi wrote:
         | In 2021, I find it hard to justify using a dynamically typed
         | language for any project that exceeds a few hundreds of lines.
         | It's not a trade off, it's a net loss.
         | 
         | The current crop of statically typed languages (from the oldest
         | ones, e.g. C#, to the more recent ones, e.g. Kotlin and Rust)
         | is basically doing everything that dynamically typed languages
         | used to have a monopoly on, but on top of that, they offer
         | performance, automatic refactorings (pretty much impossible to
         | achieve on dynamically typed languages without human
         | supervision), fantastic IDE's and debuggability, stellar
         | package management (still a nightmare in dynamic land), etc...
        
           | sova wrote:
           | I must respectfully disagree with the points you've brought
           | up.
        
             | throwaway_fjmr wrote:
             | Can you elaborate why? To be honest, I don't have
             | experience with large-scale Clojure codebases, but I have
             | my fair share working on fairly hefty Python and Perl
             | projects, and I tend to think that the parent commenter is
             | mostly right. What makes you think they are incorrect?
        
               | uDontKnowMe wrote:
               | Not who you are responding to, but the common idea that
               | static types are all win and no cost has become very
               | popular these days, but isn't true, it's just that the
               | benefits of static typing are immediately apparent and
               | obvious, but their costs are more diffuse and less
               | obvious. I thought this was a pretty good write up on the
               | subject that gets at a few of the benefits
               | https://lispcast.com/clojure-and-types/
               | 
               | Just to name some of the costs of static types briefly:
               | 
               | * they are very blunt -- they will forbid many perfectly
               | valid programs just on the basis that you haven't fit
               | your program into the type system's view of how to encode
               | invariants. So in a static typing language you are always
               | to greater or lesser extent modifying your code away from
               | how you could have naturally expressed the functionality
               | towards helping the compiler understand it.
               | 
               | * Sometimes this is not such a big change from how you'd
               | otherwise write, but other times the challenge of writing
               | some code could be virtually completely in the problem of
               | how to express your invariants within the type system,
               | and it becomes an obsession/game. I've seen this run
               | rampant in the Scala world where the complexity of code
               | reaches the level of satire.
               | 
               | * Everything you encode via static types is something
               | that you would actually have to change your code to allow
               | it to change. Maybe this seems obvious, but it has big
               | implications against how coupled and fragile your code
               | is. Consider in Scala you're parsing a document into a
               | static type like.                   case class Record(
               | id: Long,           name: String,           createTs:
               | Instant,           tags: Tags,         }
               | case class Tags(           maker: Option[String],
               | category: Option[Category],           source:
               | Option[Source],         )
               | 
               | //...
               | 
               | In this example, what happens if there are new fields on
               | Records or Tags? Our program can't "pass through" this
               | data from one end to an other without knowing about it
               | and updating the code to reflect these changes. What if
               | there's a new Tag added? That's a refactor+redeploy. What
               | if the Category tag adds a new field? refactor+redeply.
               | In a language as open and flexible as Clojure, this
               | information can pass through your application without
               | issue. Clojure programs are able to be less fragile and
               | coupled because of this.
               | 
               | * Using dynamic maps to represent data allows you to
               | program _generically_ and allows for better code reuse,
               | again in a less coupled way than you would be able to
               | easily achieve in static types. Consider for instance how
               | you would do something like `(select-keys record [:id
               | :create-ts])` in Scala. You 'd have to hand-code that
               | implementation for every kind of object you want to use
               | it on. What about something like updating all updatable
               | fields of an object? Again you'll have to hardcode that
               | for all objects in scala like                   case
               | class UpdatableRecordFields(name: Option[String], tags:
               | Option[Tags])          def update(r: Record,
               | updatableFields: UpdatableRecordFields) = {           var
               | result = r           updatableFields.name.foreach(r =
               | r.copy(name = _))
               | updatableFields.tags.foreach(r = r.copy(tags = _))
               | result         }
               | 
               | all this is specific code and not reusable! In clojure,
               | you can solve this for once and for all!
               | (defn update [{:keys [prev-obj new-obj updatable-fields}]
               | (merge obj (select-keys new-fields updatable-fields)))
               | (update            {:prev-obj {:id 1 :name "ross"
               | :createTs (now) :tags {:category "Toys"}}
               | :new-obj {:name "rachel"}             :updatable-fields
               | [:name :tags]})           => {:id 1 :name "rachel"
               | :createTs (now) :tags {:category "Toys"}}
               | 
               | I think Rich Hickey made this point really well in this
               | funny rant https://youtu.be/aSEQfqNYNAc.
               | 
               | Anyways I could go on but have to get back to work,
               | cheers!
        
               | codingkoi wrote:
               | Your third point about having to encode everything isn't
               | quite true. Your example is just brittle in that it
               | doesn't allow additional values to show up causing it to
               | break when they do. That's not a feature of static type
               | systems but how you wrote the code.
               | 
               | This blog post[1] has a good explanation about it, if you
               | can forgive the occasional snarkyness that the author
               | employs.
               | 
               | In a dynamic system you're still encoding the type of the
               | data, just less explicitly than you would in a static
               | system and without all the aid the compiler would give
               | you to make sure you do it right.
               | 
               | [1]: https://lexi-lambda.github.io/blog/2020/01/19/no-
               | dynamic-typ...
        
               | uDontKnowMe wrote:
               | I've seen this article and I applaud it for addressing
               | the issue thoroughly but I still am not convinced that
               | static typing as we know it is as flexible and generic as
               | dynamic typing. Let's go at this from an other angle,
               | with a thought experiment. I hope you won't find it
               | sarcastic or patronizing, just trying to draw an analogy
               | here.
               | 
               | So, in statically typed languages, it is not idiomatic to
               | pass around heterogeneous dynamic maps, at least in
               | application code, like it is in Ruby/Clojure/etc. But one
               | analogy we can draw which could drive some intuition for
               | static typing enthusiasts is to forget about objects and
               | consider lists. It is perfectly familiar to Scala/Java/C#
               | programmers to pass around Lists, even though they're
               | highly dynamic. So now think about what programming would
               | be like if we didn't have dynamic lists, and instead
               | whenever you wanted to build a collection, you had to go
               | through the same rigamarole that you have to when
               | defining a new User/Record/Tags object.
               | 
               | So instead of being able to use fully general `List`
               | objects, when you want to create a list, that will be its
               | own custom type. So instead of                 val list =
               | List(1,2,3,4)
               | 
               | you'll have to do:                   case class List4(_0:
               | Int, _1: Int, _2: Int, _3: Int)         val list =
               | List4(1,2,3,4)
               | 
               | This represents what we're trying to do much more
               | accurately and type-safely than with dynamic Lists, but
               | what is the cost? We can't append to the list, we can't
               | `.map(...)` the list, we can't take the sum of the list.
               | Well, actually we can!                   case class
               | List5(_0: Int, _1: Int, _2: Int, _3: Int, _4)         def
               | append(list4: List4, elem: Int): List5 = List5(list4._0,
               | list4._1, list4._2, list4._3, elem)         def
               | map(list4: List4, f: Int => Int): List4 =
               | List4(f(list4._0), f(list4._1), f(list4._2), f(list4._3))
               | def sum(list4: List4): Int = list4._0 + list4._1 +
               | list4._2 + list4._3
               | 
               | So what's the problem? I've shown that the statically
               | defined list is can handle the cases that I initially
               | thought were missing. In fact, for any such operation you
               | are missing from the dynamic list implementation, I can
               | come up with a static version which will be much more
               | type safe and more explicit on what it expects and what
               | it returns.
               | 
               | I think it's obvious what is missing, it's that all this
               | code is way too specific, you can't reuse any code from
               | List4 in List5, and just a whole host of other problems.
               | Well, this is pretty much exactly the same kinds of
               | problems that you run into with static typing when you're
               | applying it to domain objects like User/Record/Car. It's
               | just that we're very used to these limitations, so it
               | never really occurs to us what kind of cost we're paying
               | for the guarantees we're getting.
               | 
               | That's not to say dynamic typing is right and static
               | typing is wrong, but I do think that there really are
               | significant costs to static typing and people don't think
               | about it.
        
               | codingkoi wrote:
               | I'm not sure I follow your analogy. I think the dynamism
               | of a list is separate from the type system. I can say I
               | have a list of integers but that doesn't limit its size.
               | 
               | I can think of instances where that might be useful and I
               | think there's even work being done in that direction in
               | things like Idris that I really know very little about.
               | 
               | There are trade offs in everything. I'm definitely a fan
               | of dynamic type systems especially things like Lisp and
               | Smalltalk where I can interact with the running system as
               | I go, and not having to specify types up front helps with
               | that. Type inference will get you close to that in a more
               | static system, but it can only do so much.
               | 
               | The value I see in static type systems comes from being
               | able to rely on the tooling to help me reason about what
               | I'm trying to build, especially as it gets larger. I
               | think of this as being something like what Doug Englebert
               | was pointing at when he talked about augmented
               | intelligence.
               | 
               | I use Python at work and while there are tools that can
               | do some pretty decent static analysis of it, I find
               | myself longing for something like Rust more and more.
               | 
               | Another example I would point to beyond the blog post I
               | previously mentioned is Rust's serde library. It totally
               | allows you to round trip data while only specifiying the
               | parts you care about. I don't think static type systems
               | are as static as most like to think. It's more about
               | knowns and unknowns and being explicit about them.
        
               | throwaway_fjmr wrote:
               | > In a language as open and flexible as Clojure, this
               | information can pass through your application without
               | issue. Clojure programs are able to be less fragile and
               | coupled because of this.
               | 
               | Or this can wreak havoc :) Nothing stops you from writing
               | Map<Object, Object> or Map[Any, Any], right?
        
               | uDontKnowMe wrote:
               | That's true! But now we'll get into what is possible vs
               | what is idiomatic, common, and supported by the
               | language/stdlib/tooling/libraries/community. If I
               | remember correctly, Rich Hickey did actually do some
               | development for the US census, programming sort of in a
               | Clojure way but in C#, before creating Clojure. But it
               | just looked so alien and was so high-friction that he
               | ended up just creating Clojure. As the article I linked
               | to points out, "at some point, you're just re-
               | implementing Clojure". That being said, it's definitely
               | possible, I just have almost never seen anyone program
               | like that in Java/Scala.
        
               | ud_visa wrote:
               | Let me address your criticism from Scala's point of view
               | 
               | > they are very blunt
               | 
               | I'm more blunt than the complier usually. I really want
               | 'clever' programs to be rejected. In rare situations when
               | I'm sure I know something the complier doesn't, there are
               | escape hatches like type casting or @ignoreVariace
               | annotation.
               | 
               | > the problem of how to express your invariants within
               | the type system
               | 
               | The decision of where to stop to encode invariants using
               | the type system totally depends on a programmer.
               | Experience matters here.
               | 
               | > Our program can't "pass through" this data from one end
               | to an other
               | 
               | It's a valid point, but can be addressed by passing data
               | as tuple (parsedData, originalData).
               | 
               | > What if there's a new Tag added? What if the Category
               | tag adds a new field?
               | 
               | If it doesn't require changes in your code, you've
               | modelled your domain wrong - tags should be just a
               | Map[String, String]. If it does, you have to
               | refactor+redeploy anyway.
               | 
               | > What about something like updating all updatable fields
               | of an object
               | 
               | I'm not sure what exactly you meant here, but if you want
               | to transform object in a boilerplate-free way, macroses
               | are the answer. There is even a library for this exact
               | purpose: https://scalalandio.github.io/chimney/! C# and
               | Java have to resort to reflection, unfortunately.
        
           | outworlder wrote:
           | > In 2021, I find it hard to justify using a dynamically
           | typed language for any project that exceeds a few hundreds of
           | lines. It's not a trade off, it's a net loss.
           | 
           | Only if you are skimping on tests. There's a tradeoff here -
           | "dynamically typed" languages generally are way easier to
           | write tests for. The expectation is that you will have plenty
           | of them.
           | 
           | Given that most language's type systems are horrible (Java
           | and C# included) I don't really think it's automatically a
           | net gain. Haskell IS definitely a net gain, despite the
           | friction. I'd argue that Rust is very positive too.
           | 
           | Performance is not dependent on the type system, it's more
           | about language specification (some specs paint compilers into
           | a corner) and compiler maturity. Heck, Javascript will smoke
           | many statically typed languages and can approach even some C
           | implementations(depending on the problem), due to the sheer
           | amount of resources that got spent into JS VMs.
           | 
           | Some implementations will allow you to specify type hints
           | which accomplish much of the same. Which is something you can
           | do on Clojure by the way.
           | 
           | Automatic 'refactorings' is also something that's very
           | language dependent. I'd argue that any Lisp-like language is
           | way easier for machines to process than most "statically
           | typed" languages. IDEs and debugability... have you ever used
           | Common Lisp? I'll take a condition system over some IDE UI
           | any day. Not to mention, there's less 'refactoring' needed.
           | 
           | Package management is completely unrelated to type systems.
           | 
           | Rust's robust package management has more to do with it being
           | a modern implementation than with its type system. They have
           | learned from other's mistakes.
           | 
           | Sure, in a _corporate_ setting, where you have little control
           | over a project that spans hundreds of people, I think the
           | trade-off is skewed towards the most strict implementation
           | you can possibly think of. Not only type systems, but
           | everything else, down to code standards (one of the reasons
           | why I think Golang got popular).
           | 
           | In 2021, I would expect people to keep the distinction
           | between languages and their implementations.
        
             | blacktriangle wrote:
             | Here's what I've noticed with my tests and dynamic
             | languages. I'll get type errors that static typing would
             | have caught. However those errors occur in places I was
             | missing testing of actual functionality. Had I had the
             | functionality tests, then the type error would have been
             | picked up by my tests. And had I just had static typing,
             | the type system would not have been enough to prove the
             | code actually works, so I would have needed tests anyways.
             | 
             | Point being, I don't really buy that a static type system
             | saves me any time writing and maintaining tests, because
             | type systems are totally unable to express algorithms. And
             | with a working test suite (which you will need regardless
             | of static vs dynamic) large refactors become just as
             | mechanical in dynamic languages as they are in static
             | languages.
        
               | tsss wrote:
               | > type systems are totally unable to express algorithms
               | 
               | You don't know much about types if you think that.
               | 
               | As for dynamic typing "helping" you to find code that you
               | need to write tests for: There are already far more
               | sophisticated static analysis tools to measure code
               | coverage.
        
           | daxfohl wrote:
           | Yeah, I had a fairly large (about a year of solo dev work)
           | app that I maintained both Clojure and F# ports of, doing a
           | compare and contrast of the various language strengths. One
           | day I refactored the F# to be async, a change that affected
           | like half the codebase, but was completed pretty mechanically
           | via changing the core lines, then following the red
           | squigglies until everything compiled again, and it basically
           | worked the first time. I then looked at doing the same to the
           | Clojure code, poked at it a couple times, and that was pretty
           | much the end of the Clojure port.
        
             | dharmaturtle wrote:
             | Hey, so my my career path has been C# (many years) -> F#
             | (couple years) -> Clojure (3 months). I understand
             | multithreading primarily through the lens of async/await,
             | and have been having trouble fully grokking the Clojure's
             | multithreading. One of the commandments of async/await is
             | don't block: https://blog.stephencleary.com/2012/07/dont-
             | block-on-async-c...
             | 
             | Which is why the async monad tends to infect everything.
             | Clojure, as far as I can tell so far, doesn't support
             | anything similar to computation expressions. So I'm
             | guessing your "poked at it a couple times" was something
             | like calling `pmap` and/or blocking a future? All my
             | multithreaded Clojure code quickly blocks the thread... and
             | I can't tell if this is idiomatic or if there's a better
             | way.
        
               | daxfohl wrote:
               | Not even. It was opening it, looking, realizing it would
               | take a couple weeks, and going back to F#. I did this a
               | couple times before fully giving up.
               | 
               | IIRC/IIUC, Clojure's async support is closer to Go's
               | (I've never used go), in the form of explicit channels.
               | Though you can wrap that in a monad pretty easily, which
               | I did for fun one day
               | (https://gist.github.com/daxfohl/5ca4da331901596ae376).
               | But neither option was easy to port AFAICT before giving
               | up.
               | 
               | Note it's possible that porting async functionality to
               | Clojure may have been easier that I thought at the time.
               | Maybe adding some channels and having them do their thing
               | could have "just worked". I was used to async requiring
               | everything above it to be async too. But maybe channels
               | don't require that, and you can just plop them in the low
               | level code and it all magically works. A very brief
               | venture into Go since then has made me wonder about that.
        
           | gnaritas wrote:
           | Not remotely true.
        
           | [deleted]
        
           | bcrosby95 wrote:
           | I find immutability way more important.
           | 
           | I don't pick Clojure for its dynamic typing, I pick it for
           | other reasons. I've tried Haskell but it really doesn't seem
           | to mesh with the way I tend to develop a program. But I would
           | love to have more static languages with the pervasive
           | immutability of Clojure.
        
             | JackMorgan wrote:
             | I really like F# for this, it's like Haskell-lite
        
           | joelbluminator wrote:
           | It's your opinion though, there's nothing scientific about
           | what you're saying. Take mocking for example, in Ruby/Rails
           | it's a breeze. In Java you need to invent a dependency
           | injection framework (Spring) to do it.
        
             | de_keyboard wrote:
             | The best response from the statically-typed world is
             | functional programming and explicit dependencies (Haskell,
             | OCaml, F#), which makes mocking unnecessary most of the
             | time. OOP (Java, C#) is not the true standard for static-
             | typing, just the most common one.
        
             | throwaway_fjmr wrote:
             | I think you are mistaken. Mocking and DI frameworks are two
             | unrelated concepts. There is nothing in Java that forces
             | you to use a DI framework, e.g., Spring if you want to use
             | mocks during testing.
        
               | joelbluminator wrote:
               | Let's say I have a class called User and in it a method
               | that says the current time. So User#say_current_time
               | which simply accesses the Date class (it takes no
               | arguments).
               | 
               | Can you show me how you would mock the current time of
               | that method in Java?
               | 
               | It's one line of Ruby/Javascript code to do that.
        
               | lkitching wrote:
               | If you want to use DI, in java 8 you could inject a
               | java.time.Clock instance in the constructor and provide a
               | fixed instance at the required time in your test e.g.
               | Instant testNow = ...         User u = new
               | User(Clock.fixed(testNow, ZoneOffset.UTC));
               | u.sayCurrentTime();
               | 
               | although it would be better design to have sayCurrentTime
               | take a date parameter instead of depending on an external
               | dependency.
        
               | joelbluminator wrote:
               | Yes that was my point. You don't need DI or to structure
               | your code any differently in Ruby/JS/Python. You just
               | mock a method.
        
               | lkitching wrote:
               | In my experience the need to mock out individual methods
               | like this is an indication that the code is badly
               | structured in the first place. The time source is
               | effectively a global variable so in this example you'd
               | want to pass the time as a parameter to `sayCurrentTime`
               | and avoid the need to mock anything in the first place. A
               | lot of C#/java codebases do seem to make excessive use of
               | mocks and DI in this way though.
        
               | throwaway_fjmr wrote:
               | I am assuming this is easier in Ruby because you can
               | monkey patch classes?
               | 
               | Mockito in Java has a nifty way of doing this with
               | Mockito.mockStatic:                 @Test       public
               | void mockTime() throws InterruptedException {
               | LocalDateTime fake = LocalDateTime.of(2021, 7, 2, 19, 0,
               | 0);              try (MockedStatic<LocalDateTime> call =
               | Mockito.mockStatic(LocalDateTime.class)) {
               | call.when(LocalDateTime::now).thenReturn(fake);
               | assertThat(LocalDateTime.now()).isEqualTo(fake);
               | Thread.sleep(2_000);
               | assertThat(LocalDateTime.now()).isEqualTo(fake);
               | }              LocalDateTime now = LocalDateTime.now();
               | assertThat(now).isAfter(fake);
               | assertThat(now).isNotEqualTo(fake);       }
               | 
               | Or you can pass a Clock instance and use .now(clock).
               | That Clock then can be either a system clock or a fixed
               | value.
        
               | joelbluminator wrote:
               | > I am assuming this is easier in Ruby because you can
               | monkey patch classes?
               | 
               | Yes, that was my point. I see it's possible in Java
               | though, hurts my eyes a bit but possible :)
        
               | mypalmike wrote:
               | I'll take clean contractual interfaces (aka actual
               | principle of least surprise) over "I can globally change
               | what time means with one line of code!" on large projects
               | every time.
        
               | hota_mazi wrote:
               | User mock = mock(User.java)
               | when(mock.say_current_time()).thenReturn(someDate)
        
               | joelbluminator wrote:
               | OK. first I could be ignorant about Java since I haven't
               | touched it in more than a decade. Which library is doing
               | that? And also what is mock(User.java) returning - is it
               | an actual User instance or a stub? I want a real User
               | instance (nothing mocked in it) with just the one method
               | mocked.
               | 
               | And again if this is possible I will admit ignorance and
               | tip my hat at the Java guys.
        
               | throwaway_fjmr wrote:
               | I think what you want is a "spy" (partial mock), not a
               | full "mock", but yes, both are possible. You can
               | partially mock classes, i.e., specific methods only.
               | Syntax is almost the same, instead of mock(User.class)
               | you write spy(User.class).
        
               | hota_mazi wrote:
               | It's Mockito [1], which has been a standard for a while.
               | There are other libraries and they use different
               | strategies to provide this kind of functionalities
               | (dynamic proxies, bytecode weaving, annotation
               | processing, etc...).
               | 
               | [1] https://site.mockito.org/
        
               | joelbluminator wrote:
               | And ... is the whole user being mocked or just the
               | method?
        
               | vincnetas wrote:
               | It creates a stub, but you can also configure it to pass
               | any method calls to original implementation. You should
               | be tiping your hat i think.
               | 
               | https://javadoc.io/static/org.mockito/mockito-
               | core/3.11.2/or...
               | 
               | User mock = mock(User.java)
               | 
               | Should have been
               | 
               | User mock = mock(User.class)
        
               | hota_mazi wrote:
               | Ah oops, I've been writing exclusively Kotlin for several
               | years, my Java is becoming rusty (no pun intended).
        
               | mmcdermott wrote:
               | In theory, I agree, but I don't think that holds terribly
               | true in practice.
               | 
               | One of the ideas behind IoC frameworks (which build on
               | top of DI) is that you could swap out implementation
               | classes. For a great deal of software (and especially in
               | cloud-hosted, SaaS style microservice architecture) the
               | test stubs are the only other implementations that ever
               | get injected.
               | 
               | Most code bases could ditch IoC if Java provided a
               | language-level construct, even if that construct were
               | only for the test harness.
        
             | hota_mazi wrote:
             | The fact that there are such libraries in existence means
             | that there is no pain associated to this particular
             | activity. Not only do you get great mocking frameworks,
             | they are actually very robust and benefit from static
             | types.
             | 
             | Mocking dynamically typed languages is monkey patching,
             | something that the industry has been moving away from for
             | more than a decade. And for good reasons.
        
               | joelbluminator wrote:
               | > The fact that there are such libraries in existence
               | means that there is no pain associated to this particular
               | activity
               | 
               | I can say the same about Rails + RSpec. It exists
               | therefore it's good.
               | 
               | > Mocking dynamically typed languages is monkey patching,
               | something that the industry has been moving away
               | 
               | That's a reach. There are millions of
               | javascript/python/php/ruby/elixir devs that don't use
               | types or annotations. They mock. "The industry" isn't one
               | cohesive thing.
        
           | [deleted]
        
         | geokon wrote:
         | I've admittedly not played with spec, but can't you solve
         | documenting interfaces by defining `defrecord`s ? You rarely
         | really care about the actual types involved. You just want to
         | know which fields you either need to provide or will recieve
        
           | roenxi wrote:
           | Spec will give you stronger feedback than a docstring or
           | function signature. It can tell you (in code terms, with a
           | testable predicate) if a call to an interface wouldn't make
           | sense.
           | 
           | Eg, spec can warn you when an argument doesn't make sense
           | relative to the value of a second argument. Eg, with
           | something like (modify-inventory {:shoes 2} :shoes -3) spec
           | could pick up that you are about to subtract 3 from 2 and
           | have negative shoes (impossible!) well before the function is
           | called - so you can test elsewhere in the code using spec
           | without having to call modify-inventory or implement
           | specialist checking methods. And a library author can pass
           | that information up the chain without clear English
           | documentation and using only standard parts of the language.
           | 
           | You can't do that with defrecord, but it is effectively a
           | form of documentation about how the arguments interact.
        
             | modernerd wrote:
             | Does the spec logic typically live inside the modify-
             | inventory function, or elsewhere? If elsewhere, what
             | triggers it before the function is called?
        
               | teataster wrote:
               | There is very little spec logic. It looks a lot like type
               | declarations in typed languages.
               | 
               | It's usually outside the scope of functions, since you
               | are likely going to want to reuse those declarations. For
               | example, you can use spec to generate test cases for
               | something like quick-check.
               | 
               | You can add pre and post conditions to clojure function's
               | metadata that test wether the spec complies with the
               | function's input/output.
        
         | k__ wrote:
         | I think, the big issue with dynamic typing in popular languages
         | like PHP and JavaScript are the automatic conversions.
        
           | robertlagrant wrote:
           | This is a consequence of weak typing rather than dynamic
           | typing. I appreciate that these are not precise terms, but
           | being able to change something's type (dynamic) is different
           | to the language just doing strange things when you combine
           | types (weak).
        
           | dgb23 wrote:
           | You mean implicit type conversions? That's a thing you can
           | get somewhat used to. But it throws off beginners and can
           | introduce super weird bugs, because they hide bugs in weird
           | ways, even if you are more experienced. Yes, I find strong
           | typing strictly better than weak typing.
           | 
           | An even better example of this would be Excel, the horror
           | stories are almost incredible.
           | 
           | So even if your environment is dynamic, you want clarity when
           | you made a mistake. Handling errors gracefully and hiding
           | them are very different things. The optimal in a dynamic
           | world is to facilitate reasoning while not restricting
           | expression.
        
             | dangerbird2 wrote:
             | It's always worth reminding folks that weak typing and
             | implicit conversions can plague statically typed languages.
             | C's implicit pointer array-to-pointer and pointer-type
             | conversions are a major source of bugs for beginner and
             | experienced programmers alike.
        
           | elwell wrote:
           | Which is less of a concern considering Clojure's focus on
           | immutability.
        
             | k__ wrote:
             | That's what I meant.
             | 
             | As far as I know, some dynamic languages like Python don't
             | have that issue.
        
         | vnorilo wrote:
         | Agreed. I feel Lisps and SmallTalk are dynamic done right. I
         | think the other language features that you use also influence
         | the value from dynamic or static types. For OOP style, static
         | types are a huge asset for refactoring and laying our
         | architecture. On the other hand, immutable data and stateless
         | functions (as idiomatic in clojure) make them less necessary,
         | and also work great together with interactive development.
        
         | pjmlp wrote:
         | Not only that, Smalltalk and Lisps are languages designed with
         | developer experience as part of the language.
         | 
         | You just don't get an interpreter/compiler and have to sort
         | everything else by yourself, no, there is a full stack
         | experience and development environment.
        
         | c-cube wrote:
         | Except, of course, that specs are only tested correct, not
         | proven correct like types would be. Types (in a reasonable
         | static type system, not, say, C) are never wrong. In addition,
         | specs do not compose, do they ? If you call a function g in a
         | function f, there is no automatic check that their specs align.
        
           | undershirt wrote:
           | Yeah, and I think this is obvious, but it certainly depends
           | on the _origin_ of the data being checked. We can prove the
           | structure of "allowed" data ahead of time if we want
           | guarantees on what's possible inside our program. We also
           | want a facility to check data encountered by the running
           | program (i.e. from the user or another program.) which of
           | course we can't know ahead of time.
           | 
           | It is a design decision to be able to build a clojure system
           | interactively while it is running, so a runtime type checker
           | is a way for the developer to give up the safety of type
           | constraints for this purpose--by using the same facility we
           | already need in the real world, a way to check the structure
           | of data we can't anticipate.
        
           | travisjungroth wrote:
           | > Types (in a reasonable static type system, not, say, C) are
           | never wrong.
           | 
           | Oh man. This is the fundamental disagreement. Sure, you can
           | have a type system that is never wrong in its own little
           | world. But, that's not the problem. A lot of us are making a
           | living mapping real world problems into software solutions.
           | If that mapping is messed up (and it always is to some
           | degree) then the formal correctness of the type system
           | doesn't matter _at all_. It 's like you got the wrong answer
           | really, really right.
        
             | lolinder wrote:
             | > A lot of us are making a living mapping real world
             | problems into software solutions. If that mapping is messed
             | up (and it always is to some degree) then the formal
             | correctness of the type system doesn't matter at all.
             | 
             | If I'm understanding you correctly, you're saying
             | statically typed language can't protect against design
             | flaws, only implementation flaws. But implementation flaws
             | are common, and statically typed languages _do_ help to
             | avoid those.
        
             | c-cube wrote:
             | I'm not saying types always model your problem properly!
             | That's not even well specified. I'm saying that "x has type
             | foo" is never wrong if the program typechecks properly.
             | That's totally different, and it means that you can rely on
             | type annotations as being correct, up-to-date
             | documentation. You can also trust that functions are never
             | applied to the wrong number of arguments, or the wrong
             | types; my point is that this guarantees more, in a less
             | expressive way, than specs.
        
               | travisjungroth wrote:
               | You can statically analyze specs and check them at
               | runtime if you want.
        
           | cle wrote:
           | > Except, of course, that specs are only tested correct, not
           | proven correct like types would be.
           | 
           | Yes this is the fundamental tradeoff. Specs et al are
           | undoubtedly more flexible and expressive than static type
           | systems, at the expense of some configurable error tolerance.
           | I don't think one approach is generally better than the
           | other, it's a question of tradeoffs between constraint
           | complexity and confidence bounds.
        
           | dgb23 wrote:
           | Yes, I think that is one of the big weaknesses of it. You can
           | write specs that make no sense and it will just let you. So
           | far there is also no way to automatically check whether you
           | are strengthening a guarantee or weaken your assumptions
           | relative to a previous spec. In a perfect world we would have
           | this in my opinion.
        
         | travv0 wrote:
         | The very first property testing library was written in Haskell,
         | as far as I know.
        
         | blacktriangle wrote:
         | I feel like there's a missing axis in the static/dynamic
         | debate: the language's information model.
         | 
         | In an OOP language, types are hugely important, because the
         | types let you know the object's ad-hoc API. OOP types are
         | incredibly complicated.
         | 
         | In lisps, and Clojure in particular, your information model is
         | scalars, lists, and maps. These are fully generic structures
         | whose API is the standard Clojure lib. This means that its both
         | far easier to keep the flow of data through your program in
         | your head.
         | 
         | This gives you a 2x2 matrix to sort languages into, static vs
         | dynamic, and OOP vs value based.
         | 
         | * OOP x static works thanks to awesome IDE tooling enabled by
         | static typing
         | 
         | * value x static works due to powerful type systems
         | 
         | * value x dynamic works due to powerful generic APIs
         | 
         | * OOP x dynamic is a dumpster fire of trying to figure out what
         | object you're dealing with at any given time (looking right at
         | you Python and Ruby)
        
         | scotty79 wrote:
         | > ... such as arbitrary predicate validation, freely composable
         | schemas, automated instrumentation and property testing ...
         | 
         | Why static typing makes those things impossible?
        
           | dgb23 wrote:
           | They don't make these impossible, they typically just don't
           | let you express these within the type system and they
           | typically don't let you _not_ specify your types.
           | 
           | I should have made clear that I'm emphasizing the advantages
           | of being dynamic to describe and check the shape of your data
           | to the degree of your choosing. Static typing is very
           | powerful and useful, but writing dynamic code interactively
           | is not just "woopdiedoo" is kind of the point I wanted to
           | make without being overzealous/ignorant.
        
             | scotty79 wrote:
             | I think TypeScript is best of both worlds.
             | 
             | Typesystem strong enough to express dynamic language and
             | completely optional wherever you want.
        
             | thinkharderdev wrote:
             | That largely depends on the type system. Languages like
             | Haskell and Scala which have much more powerful type
             | systems than C/Java/Go/etc absolutely do allow you to do
             | those sorts of things. It is a bit harder to wrap your head
             | around to be sure and there are some rough edges, but once
             | you get the hang of it you can get the benefits of static
             | typing with the flexibility of dynamic typing. See
             | https://github.com/milessabin/shapeless or a project that
             | I've been working on a lot lately
             | https://github.com/zio/zio-schema.
        
         | dharmaturtle wrote:
         | > What you get here goes way beyond what a strict, static type
         | systems gets you, such as arbitrary predicate validation,
         | 
         | Is this refinement types, which most static languages provide?
         | https://en.wikipedia.org/wiki/Refinement_type
         | 
         | > freely composable schemas,
         | 
         | My understanding is that you can compose types (and objects)
         | https://en.wikipedia.org/wiki/Object_composition
         | 
         | I'm assuming that types are isomorphic with schemas for the
         | purposes of this discussion.
         | 
         | > automated instrumentation
         | 
         | I know that C# and F# support automated
         | instrumentation/middleware.
         | 
         | > and property testing. You simply do not have that in a static
         | world.
         | 
         | QuickCheck has entered the chat:
         | https://en.wikipedia.org/wiki/QuickCheck
        
           | CraigJPerry wrote:
           | >> Is this refinement types
           | 
           | Well it does include that kind of behaviour but it's quite a
           | bit more than just that. E.g. you could express something
           | like "the parameter must be a date within the next 5 business
           | days" - there's no static restriction. I'm not necesarily
           | saying you should but just to give an illustrative example
           | that there's less restrictions on your freedom to express
           | what you need than in a static system.
           | 
           | >> types are isomorphic with schemas
           | 
           | I don't think that's a good way to think of this, you're
           | imagining a rigid 1:1 tie of data and spec yet i could swap
           | out your spec for my spec so that would be 1:n but those
           | specs may make sense to compose in other data use cases so
           | really it's m:n rather than 1:1
        
             | dharmaturtle wrote:
             | > E.g. you could express something like "the parameter must
             | be a date within the next 5 business days" - there's no
             | static restriction
             | 
             | Hm, I don't follow. If I were to write this in F#, there
             | would be a type `Within5BusinessDays` with a private
             | constructor that exposes one function/method `tryCreate`
             | which returns a discriminated union: either an `Ok` of the
             | `Within5BusinessDays` type, or an `Error` type with some
             | error message. Once I have the type, I can then compose it
             | with whatever and send it wherever and since F# records are
             | immutable, I won't have to worry about invariants not
             | holding. And since it's a type, I have the compiler/type
             | system on my side to help with correctness.
             | 
             | (Side note, this is a bad example since the type can become
             | invalid after literally 1 second... but since Clojure has
             | the same problem I'm just running with it.)
             | 
             | I'm still learning Clojure (only a few months into it), but
             | if I were to to write a spec, I'd have to specify what to
             | do do if the spec failed to conform - same as returning the
             | `Error` case in F#.
             | 
             | > i could swap out your spec for my spec so that would be
             | 1:n but those specs may make sense to compose in other data
             | use cases so really it's m:n rather than 1:1
             | 
             | Sorry, but I'm still not following - I believe you can do
             | the same with types, especially if the type system support
             | generics.
        
               | CraigJPerry wrote:
               | > If I were to write this in F#, there would be a type
               | `Within5BusinessDays`
               | 
               | That's not really the same thing - it'sa valid
               | alternative approach but you've lost the benefits of a
               | simple date - from (de)serialisation to the rich support
               | for simple date types in libraries and other functions,
               | the simple at-a-glance understanding that future readers
               | could enjoy. Now the concept of date has been complected
               | with some other niche concern.
               | 
               | > the type can become invalid after literally 1 second
               | 
               | Every system I've ever seen that has the concept of a
               | business date strictly doesn't derive it from wall clock
               | date. E.g. it's common that business date would be rolled
               | at some convenient time (and most often not midnight) so
               | you'd be free to ensure no impacts possible from the date
               | roll.
               | 
               | >> I believe you can do the same with types, especially
               | if the type system support generics
               | 
               | You can do something similar but you'll need to change
               | the system's code.
               | 
               | It would be almost like gradual typing, except you could
               | further choose to turn it off or to substitute your own
               | types / schema without making changes to the system /
               | code.
               | 
               | It's quite a lot more flexible.
               | 
               | (Apols for slow reply - i 1up'd your reply earlier when i
               | saw it but couldn't reply then)
        
               | dharmaturtle wrote:
               | > but you've lost the benefits of a simple date
               | 
               | I see what you mean - thanks!
               | 
               | > you could further choose to turn it off or to
               | substitute your own types / schema without making changes
               | to the system / code
               | 
               | This is still unclear to me. How can you make changes
               | (turning off gradual typing/substituting your own schema)
               | without making changes to code?
        
           | dgb23 wrote:
           | Right! I made the dumb, typical error to write: "You simply
           | do not have that in a static world." When I should have
           | written: "This type of expressiveness is not available in
           | mainstream statically typed languages".
           | 
           | With "freely composable" I mean that you can program with
           | these schemas as they are just data structures and you only
           | specify the things you want to specify. Both advantage and
           | the disadvantage is that this is dynamic.
        
             | dharmaturtle wrote:
             | Ah, well if you're going to shit on Go/Java/C#/C++ I won't
             | stop you :)
        
       | scotty79 wrote:
       | I tried to use Clojure but what put me of was that simple
       | mistakes like missing argument or wrongly closed bracket didn't
       | alert me until I tried running the program and then gave me just
       | some java stack spat out by jvm running Clojure compiler on my
       | program.
       | 
       | It didn't feel like a first class experience.
        
         | capableweb wrote:
         | > didn't alert me until I tried running the program
         | 
         | That's because that's not how Clojure developers normally work.
         | You don't do changes and then "run the program". You start your
         | REPL and send expressions from your editor to the REPL after
         | you've made a change you're not sure about. So you'd discover
         | the missing argument when you call the function, directly after
         | writing it.
        
           | scotty79 wrote:
           | Interesting. How exactly that looks? Do you have files opened
           | in your editor, change them then go into previously opened
           | repl, and just call the functions and the new version of
           | those function runs?
        
             | finalfantasia wrote:
             | Thanks to the dynamic nature of Clojure programs,
             | experienced Clojure developers use the REPL-driven
             | development workflow as demonstrated in this video [1].
             | 
             | [1] https://youtu.be/gIoadGfm5T8
        
               | scotty79 wrote:
               | From what I understand, instead of writing the file and
               | running the file you write separate statements in the
               | file and evaluate each of them in the repl (like with "Do
               | it" in Smalltalk).
               | 
               | So what you get, after running the file afterwards from
               | clean state might be different than the result of your
               | selective separate manual evaluations.
               | 
               | This looks like exactly the opposite of the F5 workflow
               | in the browser where you can run your program from clean
               | state with single keypress.
               | 
               | I haven't watched the video till the end though maybe
               | there's a single key that restarts the repl and runs the
               | files from clean state here too.
               | 
               | At first glance you could have the same workflow with JS,
               | but there's not much need for it because JS VMs restart
               | very quickly and also you'd need to code in JS in very
               | particular style, avoiding passing function and class
               | "pointers" around and avoid keeping them in variables. I
               | guess clojure just doesn't do that very often and just
               | refers to functions through their global identifiers, and
               | if that's not enough, even through symbols (like passing
               | the #'app in this video instead of just app).
        
             | uDontKnowMe wrote:
             | That's right. You typically would have your text editor/ide
             | open, and the process you're developing would expose a repl
             | port which your editor can connect to. As you edit the
             | source code, that will automatically update the code
             | running in the process you're debugging. See this demo of
             | developing a ClojureScript React Native mobile app
             | published yesterday: https://youtu.be/3HxVMGaiZbc?t=1724
        
               | [deleted]
        
       | pron wrote:
       | > We tried VisualVM but since Clojure memory consists mostly of
       | primitives (Strings, Integers etc) it was very hard to understand
       | which data of the application is being accumulated and why.
       | 
       | You should try deeper profiling tools like JFR+JMC
       | (http://jdk.java.net/jmc/8/) and MAT
       | (https://www.eclipse.org/mat/).
        
         | gavinray wrote:
         | I was going to suggest this -- inside of VisualVM, you can
         | right-click a process and then press "Start JFR"
         | 
         | Then wait a bit, right click it again, and select "Dump JFR"
         | 
         | What you get is a Flight Record dump that contains profiling
         | information you can view that's more comprehensive than any
         | language I've ever seen.
         | 
         | I used this for the first time the other day and felt like my
         | life has been changed.
         | 
         | Specifically, if you want to see where the application is
         | spending it's time and in what callstacks, you can use the CPU
         | profiling and expand the threads -- they contain callstacks
         | with timing
         | 
         | There's some screenshots in an issue I filed here showing this
         | if anyone if curious what it looks like:
         | 
         | https://github.com/redhat-developer/vscode-java/issues/2049
         | 
         | Thanks Oracle.
        
       | user3939382 wrote:
       | Walmart Labs was a step in this direction.. but we need some big
       | companies to standardize around Clojure to jumpstart the
       | ecosystem of knowledge, libraries, talent, etc. I've spoken to
       | engineering hiring managers at fairly big companies and they're
       | not willing to shift to a niche language based only on technical
       | merits but without a strong ecosystem.
       | 
       | If we don't get some big companies to take on this roll the
       | language is going nowhere.
       | 
       | I'm saying this because I'm a huge fan of Clojure (as a syntax
       | and language, not crazy about the runtime characteristics) and I
       | hope I get the opportunity to use it.
        
         | iLemming wrote:
         | > If we don't get some big companies to take on this roll
         | 
         | - Cisco - has built their entire integrated security platform
         | on Clojure
         | 
         | - Walmart Labs and Sam's club - have some big projects in
         | Clojure
         | 
         | - Apple - something related to the payment system
         | 
         | - Netflix and Amazon, afaik they use Clojure as well
         | 
         | even NASA uses Clojure.
         | 
         | I think the language "is going somewhere"...
        
           | user3939382 wrote:
           | role* lol we made the same mistake. There is some adoption to
           | be sure. But look at Google Trends for clojure.
        
       | ivanech wrote:
       | I started working professionally with Clojure earlier this year
       | and this article rings true. I think the article leaves out a
       | fourth downside to running on the JVM: cryptic stack traces.
       | Clojure will often throw Java errors when you do something wrong
       | in Clojure. It's a bit of a pain to reason about what part of
       | your Clojure code this Java error relates to, especially when
       | just starting out.
        
         | alaq wrote:
         | How did you make the switch? Were you already working for the
         | same company? Did you already know Clojure, from open source,
         | or side projects?
        
           | ivanech wrote:
           | I work at Ladder [0], and almost everything is done in
           | Clojure/ClojureScript here. I had no previous experience in
           | Clojure - Ladder ramps you if you haven't used it before. My
           | interview was in Python. We're currently hiring senior
           | engineers, no Clojure experience necessary [1].
           | 
           | [0] https://www.ladderlife.com/
           | 
           | [1] https://boards.greenhouse.io/ladder33/jobs/2436386
        
         | finalfantasia wrote:
         | To be fair, this is not unique to Clojure. You need to deal
         | with stack traces no matter what as long as you're using any
         | programming language that targets the JVM (even statically
         | type-checked languages like Scala). There are some great
         | articles [1][2] that discuss various simple techniques helpful
         | for debugging and dealing with stack traces.
         | 
         | [1] https://eli.thegreenplace.net/2017/notes-on-debugging-
         | clojur...
         | 
         | [2] https://cognitect.com/blog/2017/6/5/repl-debugging-no-
         | stackt...
        
           | rockostrich wrote:
           | I've never really had a problem with stack traces in Scala.
           | Every once in a while you hit a cryptic one that's buried in
           | Java library code, but for the most part they're runtime
           | errors that are due to incompletely tested code or some kind
           | of handled error with a very specific message.
        
       | aliswe wrote:
       | > ... and the question regarding choosing Clojure as our main
       | programming language rose over and over again
       | 
       | If I find myself having to repeat myself justifying a certain
       | decision time and time again, it's an indicator that the decision
       | needs to be revised to be something which is a more intuitive fit
       | for the organization.
        
         | outworlder wrote:
         | That's not a good indication that the decision was or was not
         | correct. Only that it currently runs against whatever the
         | established practice is. Sometimes "the way things have always
         | been done" is just wrong.
         | 
         | This is unlikely to be the case in the choice of programming
         | languages. Some may be a bad fit, some may have ecosystems that
         | are unpleasant to use, but it's generally not the biggest
         | problem an organization will have.
        
         | lostcolony wrote:
         | Not really; it's like stoplights. You're going to be
         | interrupted and therefore notice the red lights, and just sail
         | easily through and thus not notice the green lights. Likewise,
         | you're going to notice the pain points, but need to take a
         | minute to reflect to notice the benefits.
         | 
         | Really, if repeating the same justifications convinces people,
         | then the problem isn't the justifications.
        
         | joelbluminator wrote:
         | I donno why you're being downvoted, it's a questionable
         | decision and probably the company would have been better off
         | with Python/PHP/Node. Hiring and onboarding are extremely
         | important for a startup. You know what else? Finding answers to
         | common questions on Google/Stackoverflow; I am now working with
         | Ember and can tell you guys you take a 50% productivity hit by
         | using a tool that's obscure on Google. Sure once you become
         | super familiar with a tool that matters less, but that takes
         | time. Much more time. React/Angular may be an inferior tool to
         | Ember but the fact that you can get answers to almost any
         | question is priceless. The community size is super important.
         | The frameworks are super important (is there a Closure
         | equivalent to Rails/Django/Laravel in community size, in battle
         | testedness? I really doubt it).
         | 
         | That being said, I salute these brave companies for sticking to
         | these obscure languages. Do we want to live in a world where
         | there's only 3 languages to do everything? Even 10 sounds
         | boring. Hell, even a fantastic tool like Ruby is considered
         | Niche in certain parts of the world. I don't want a world
         | without Ruby so I don't want a world without Closure.
        
         | yakshaving_jgt wrote:
         | Alternatively, you could document the thought process that lead
         | up to the decision and you can point the unenlightened to the
         | documentation instead of having to repeat yourself.
        
       ___________________________________________________________________
       (page generated 2021-08-02 23:01 UTC)