[HN Gopher] Tour of our 250k line Clojure codebase
       ___________________________________________________________________
        
       Tour of our 250k line Clojure codebase
        
       Author : grzm
       Score  : 217 points
       Date   : 2021-06-03 18:12 UTC (4 hours ago)
        
 (HTM) web link (tech.redplanetlabs.com)
 (TXT) w3m dump (tech.redplanetlabs.com)
        
       | logistark wrote:
       | I would like to ask if you are comfortable of Clojure protocols,
       | because i tend to avoid them. What do you thik about it?
        
       | nickik wrote:
       | I don't really like 'Component'. I seems very clunky and we had a
       | lot of issues with it and a lot incidental complexity in our
       | codebase (now converted to Java). It was the first real system
       | that did these sort of things but if I start a project now, I
       | much rather use Integrant or Clip.
       | 
       | https://github.com/weavejester/integrant
       | 
       | https://github.com/juxt/clip
       | 
       | I haven't used Clip a lot yet but my next project is defiantly
       | going to be with Clip.
       | 
       | For validation I have started to use Malli
       | (https://github.com/metosin/malli). I really liked the idea
       | behind clojure.spec but some of the implementation was a bit
       | clunky and a bit too 'Rich Hickey', not sure how to describe it
       | otherwise. Schema was again the first serious attempt at a
       | library like that for Clojure so it was really nice when it came
       | out. Malli sort of combines what is great about both.
        
       | amackera wrote:
       | Thank you for sharing this detailed rundown. One of the
       | challenges that I faced as a new Clojure developer was
       | understanding how all the parts fit together (and in fact, what
       | parts I should care about in the first place). Example: is
       | learning Component worth it? (yes)
       | 
       | Extremely glad to see this resource published! Thank you to the
       | Red Planet team for releasing this!
        
       | [deleted]
        
       | bradleybuda wrote:
       | > Detecting an error when creating a record is much better than
       | when using it later on, as during creation you have the context
       | needed to debug the problem.
       | 
       | This is a great insight no matter what language or framework
       | you're operating in. Laziness has its virtues, but invariants,
       | validity checks, run-time type checks, etc. should all be
       | performed as early (and often) as possible - it's much easier to
       | debug issues when the are proximate to an object or structure
       | being created or modified, then when that data type is being used
       | much later.
        
         | vbsteven wrote:
         | Yes, and this is where statically typed languages shine (in my
         | opinion). I like programming in a style that makes heavy use of
         | the type system to enforce this.
         | 
         | For example when writing an api endpoint to create a task I
         | would typically deserialise the json into a CreateTaskRequest.
         | If the object is created without exceptions I can be sure it is
         | valid. CreateTaskRequest implements the ToTask interface. The
         | service layer takes only objects of this interface and converts
         | into a Task object that gets persisted. The persisted Task then
         | gets converted into a TaskResponse so only valid JSON comes
         | back out.
         | 
         | Lots of classes and interfaces but they are all small and with
         | a single purpose.
        
           | kgwxd wrote:
           | > If the object is created without exceptions I can be sure
           | it is valid.
           | 
           | Without some kind of custom validation system in place (JSON
           | schema, property attributes, etc), that doesn't tell you
           | much. With most serialization libraries I've used the default
           | settings would let you deserialize {} into any class without
           | an exception and all the properties would just have the
           | default value for their type. Using stricter setting gets you
           | a little more but definitely no guarantee of a logically
           | valid state. If I want to go even one step past what little
           | validation static typing provides, and I usually do, I'd
           | rather just take the type noise completely out of my data and
           | move all validation to a single place.
        
             | dan-robertson wrote:
             | Strongly depends on the language. If you have ML style data
             | types (eg ML, Haskell, OCaml, Rust) ands some way to
             | abstract types (ML structs, Haskell modules hiding type
             | constructors, OCaml modules, Rust modules) then it is
             | generally possible to design your data structures in such a
             | way that invalid states cannot be represented. If a user
             | has an optional first/last name but if one is specified
             | then so is the other, your user type has a name of type
             | (eg) Maybe (string, string) [1]. If your deserialisation
             | framework just fills in default values for everything or
             | let's you have unused fields then it is, in my opinion,
             | broken. If you have a field that should be a positive
             | bumber, you should have a type for a positive number that
             | fails to deserialise if you give it a negative number.
             | 
             | [1] the caveat is that it somewhat sucks to change these
             | restrictions and therefore the types. Compilers can
             | hopefully make these refractors easier, but they may still
             | suck. In languages like clojure, you mostly have to try to
             | write programs/tests to be resilient to any reasonable
             | changes to the data structures that you can imagine.
        
             | TeMPOraL wrote:
             | You implement your own custom validation. The point is to
             | encode the fact that you've validated a value in its type.
             | So you have e.g. DeserializeJSON<Foo>(String) -> Foo, and
             | then ValidateFoo(Foo) -> ValidatedFoo. And then all the
             | business code works on ValidatedFoo.
        
               | kgwxd wrote:
               | I know the point, I'm a C# dev by trade, but if I'm going
               | to implement validation that goes beyond static type
               | checking, which is most of the time, I'd rather put it
               | all in a single place and not have to deal with the type
               | nuisance in every single line of code I write.
        
             | vbsteven wrote:
             | That depends on the language and deserialisation library
             | used. And that is also the reason why I have multiple
             | classes.
             | 
             | My CreateTaskRequest class which is used for
             | deserialisation only does not have defaults (unless
             | intentional) so it throws when required values are not
             | present in the source json. It also takes care of
             | dateformat/uuid parsing. The output is always fully valid
             | typed or it throws. It's a 1-to-1 mapping on what is
             | described in the API docs.
             | 
             | The service layer that takes CreateTaskRequest and converts
             | to Task for persistence is where the business logic
             | validation happens (valid foreign keys, date ranges, etc,
             | unique checks). Cleanly separated from deserialization.
             | 
             | For reference: I use Kotlin with Jackson which has great
             | support for this.
        
             | mumblemumble wrote:
             | At the end of the day, when we're working with external
             | data, no language is a silver bullet. But you can get
             | pretty far by doing https://lexi-
             | lambda.github.io/blog/2019/11/05/parse-don-t-va... and
             | yelling at your colleagues when they don't.
             | 
             | FWIW, this sort of thing can be done with a dynamic
             | language, too. It's true that some static languages happen
             | to be really far ahead of the curve with this sort of
             | thing. But it's also true that some dynamic languages put
             | you in a better position on this front than many of the
             | most popular static languages do.
             | 
             | (And for those of us who really do love an intractable
             | quagmire, there's always JavaScript.)
             | 
             | For my part, I tend to find this debate to be mostly a
             | distraction, because the influence of the language's type
             | discipline is quite small relative to the influence of the
             | programmer's coding discipline. In the group I'm working
             | with currently, the most committed fans of static typing
             | tend to also be the ones who have the greatest tendency to
             | assume, "If it compiles, it works," and proceed to check in
             | glaring bugs. I don't bring that up in order to point
             | fingers at static typing proponents (I tend to prefer
             | static myself, though my preference is not particularly
             | strong) so much as to point out that we should be wary of
             | memes that subtly encourage us to become complacent.
        
           | CraigJPerry wrote:
           | You're writing more code than you have to though. More code =
           | more bugs.
        
             | Quekid5 wrote:
             | Just for clarification: Do you equate type ascriptions to
             | "more code"?
        
             | vbsteven wrote:
             | I don't agree there. Most of these extra classes are just
             | type declarations with no methods at all.
             | 
             | While the total LoC written might be higher, the amount of
             | written logic in which you can introduce bugs is less.
        
               | CraigJPerry wrote:
               | For any type A -> B operation, it's possible to fail -
               | that is the whole point of parsing into type B, to catch
               | when B's invariants would not be satisfied. The bug could
               | be as simple as neglecting to handle that failure
               | scenario.
               | 
               | Some languages make this less likely (Haskell, Rust) but
               | most mainstream languages will happily let you introduce
               | this bug.
        
           | Quekid5 wrote:
           | > Yes, and this is where statically typed languages shine (in
           | my opinion).
           | 
           | Indeed. This feels like a just-before-the-moment-of-
           | realization situation.
           | 
           | The endless cycle between "more dynamic" and "more static"
           | continues it seems.
           | 
           | I wonder if there is any correlation between experience in
           | the field and static vs. dynamic vs. "fail fast dynamic".
           | 
           | (I'd say Erlang falls in the latter category and it has a
           | pretty good track record for reliability, but so does Python.
           | It's an imperfect axis for sure.)
        
           | habibur wrote:
           | > Lots of classes and interfaces but they are all small and
           | with a single purpose.
           | 
           | That's the side effect. You ultimately end up with more code
           | and not less even though it saves you from type checks.
           | 
           | There are trade offs for both.
        
             | TeMPOraL wrote:
             | One thing I wish for is some kind of "type tags". Being
             | able to express concepts like List[Widget], List[Widget,
             | Nonempty], List[Widget, Nonempty, Sorted], Vector[User,
             | Sorted], etc. - or even, more generic, <Container>[<T>,
             | NonEmpty] (where <Container> and <T> are parameters, like
             | in C++ templates) - without implementing an explicit new
             | type for each. Logic verification through typing would then
             | involve not just changing "main" types, but also adding and
             | dropping "tags" from the "set of tags" attached to the
             | "main" type. This should cut down on the amount of
             | boilerplate.
             | 
             | Hell, in the extreme, perhaps types in general could be
             | generalized as a set of tags?
             | 
             | (See also, https://news.ycombinator.com/item?id=27168893)
        
               | vbsteven wrote:
               | A language like Kotlin can do some of these things using
               | delegates, interfaces and extension methods.
               | 
               | For example a MutableList<T> can be dropped down to a
               | List<T> which is not mutable. And a generic conversion
               | from Collection<T> to NonEmptyCollection<T> should be
               | trivial to write as an extension method.
        
               | TeMPOraL wrote:
               | Can Kotlin handle multiple "tags" on a type as a set, and
               | not a sequence? I'm not familiar with the language, so
               | I'll use a C++ analogy. If you tried to tag types in C++,
               | you'd end up with something like:
               | TaggedType<std::vector, NonEmpty, Sorted>
               | 
               | but such type is strictly not the same as:
               | TaggedType<std::vector, Sorted, NonEmpty>
               | 
               | What I mean by "set" instead of a "sequence" is to have
               | the two lines above represent the same type, i.e. the
               | order of tags should not matter.
        
               | vbsteven wrote:
               | You can maybe get there in kotlin with a generic type
               | with multiple constraints in a where clause. Let's say
               | you have Sorted and NonEmpty as interfaces (could be
               | empty marker interfaces so they behave like tags). Then
               | you can write a method                 fun <T>
               | doSomething(values: T) where T: Sorted, T: NonEmpty {}
               | 
               | And that function will take any type that has both Sorted
               | and NonEmpty interfaces.
        
             | vbsteven wrote:
             | > There are trade offs for both
             | 
             | Exactly.
             | 
             | Personally I like that bit of extra code because it gives
             | every class one reason to exist. There are no conflicts so
             | the type system can be used fully without ambiguity.
             | 
             | I've always disliked the way early rails promoted fat
             | models that combined serialisation, deserialisation,
             | validation, persistence, querying and business logic in the
             | same class.
        
               | TeMPOraL wrote:
               | I personally bounce back and forth about this. My
               | experience is probably colored by the fact that I'm doing
               | this in C++. Boilerplate gets annoying there (and
               | attempts to cut it down tend to produce lots of
               | incomprehensible function templates). I like the idea of
               | using types to encode assertions at a fine granularity. I
               | dislike the amount of tiny little functions this creates.
               | I also dislike that the resulting code is only navigable
               | with an IDE - otherwise you spend 50% of your time
               | chasing definitions of these little types.
        
               | vbsteven wrote:
               | Ok yes, C++ might not be the greatest language for this.
               | 
               | My experience here is mostly from Kotlin which is a great
               | language for this. Nullability, extension methods,
               | (reified) generics, data classes, delegates, etc can all
               | help reduce boilerplate.
        
       | chairhairair wrote:
       | Is there more info about the tool itself that claims 100X
       | decrease in application development cost? Quite the claim.
        
         | mberning wrote:
         | It does seem odd. Stealth product. Bold claims. 3 blog posts
         | one of which is a funding announcement and the other two having
         | nothing to do with the product. I am intrigued but also a bit
         | skeptical.
        
           | davidrupp wrote:
           | Nathan Marz was the original creator of what became Apache
           | Storm [1], which powered Twitter for some time. Skepticism is
           | healthy, perhaps even warranted here, but I'm not betting
           | against him just yet.
           | 
           | [1] https://en.wikipedia.org/wiki/Apache_Storm
        
             | vbsteven wrote:
             | He is also the creator of Cascalog (Hadoop query dsl in
             | Clojure) and the Lambda architecture pattern.
             | 
             | Not lambda as we know it now popularised by AWS, but an
             | architecture for stream processing where batch views from
             | expensive and slow batch jobs are combined with speed views
             | from stream processors into the final live result.
             | 
             | https://en.m.wikipedia.org/wiki/Lambda_architecture
        
         | pbiggar wrote:
         | I've heard from investors that it is similar to Darklang. It
         | certainly has the same goals, but dunno if it's the same
         | approach in any way. Will be interesting to see
        
           | neysofu wrote:
           | In that case, I wouldn't go near it with a ten foot pole.
        
         | twobitshifter wrote:
         | The team is impressive, I was wondering what all this new
         | internal language, 400 macros, etc., could be put towards,
         | thinking they were stuck in over-engineering. But after seeing
         | that promise for their app, I changed my mind. Something that's
         | capable of making you 100 times more productive probably does
         | need that level of development.
        
         | kulig wrote:
         | How do VCs still fall for this shit.
         | 
         | It is mind bogling.
        
         | dj_gitmo wrote:
         | I'm also curious. It seems like the website has been around for
         | 2 years and hasn't changed much. If they wrote 250k lines in
         | two years, that is around 340 lines a day. That seems like a
         | rather large project to build before putting it in the hands of
         | customers.
        
         | karmasimida wrote:
         | I will be very dubious of this claim, or belief is really what
         | it is. There is not even simplistic metrics to back it up
        
         | yamrzou wrote:
         | It's still in stealth mode. They raised $5M in 2019:
         | https://news.ycombinator.com/item?id=19565267
        
           | swyx wrote:
           | how exactly is it stealth if they have a blog and announced
           | funding?
        
             | yamrzou wrote:
             | I meant that they didn't reveal the product yet.
        
       | geospeck wrote:
       | > And doing things dynamically means we can enforce stronger
       | constraints than possible with static type systems.
       | 
       | Can someone please explain this to a novice like me?
        
         | jahewson wrote:
         | Taken literally the claim isn't true - a Turing complete type
         | system can enforce any constraint that a Turing complete
         | programming language can. But your everyday type systems
         | typically can't express concepts like "a list of at least 3
         | elements" or "a number which is a power of 2".
        
           | SCLeo wrote:
           | Pardon my ignorance, but can this spec thing automatically
           | deduce that 8^n evaluates to "a number which is a power of 2"
           | or log(2, "a number which is a power of 2") is an integer?
           | 
           | If yes, then I agree this is super helpful (and magical).
           | 
           | If not, how is this different from a normal constrcutor (with
           | runtime input validation)?
        
         | dgb23 wrote:
         | The biggest manifestation of this is clojure spec:
         | 
         | https://clojure.org/about/spec
         | 
         | If your impression is that this is like sugary unit tests: It
         | is not. You can run specs during development, while running a
         | in-editor REPL, code gets evaluated while you type it so to
         | speak.
         | 
         | It is way more expressive than a type system and it is opt-in,
         | but it doesn't give the same guarantees obviously. It is also
         | not meant to be a type system but rather a tool to express the
         | shape of your data. It is used for obvious things like
         | validation but also for documentation (over time) and
         | generative testing among other things.
        
         | elmers-glue wrote:
         | Most static time type systems can enforce that a voting age is
         | an Integer, say, but can't validate that any value is at least
         | 18, for example.
        
           | StreamBright wrote:
           | This is not really true if you include ML languages. Most of
           | the time you create a specific type for a type that is
           | constrained. Ada has pretty good support for this and ML
           | languages too. Once you have a specific type you make all
           | your functions accept only VoterAge instead of Int.
        
             | Zababa wrote:
             | I think this would work with every language that has
             | nominal and not structural typing. If you have structural
             | typing, you have to wrap the int. For example, this is
             | "branding" in Typescript. I'm not sure if there is a
             | performance penalty and how big it is though.
        
           | Zababa wrote:
           | They can, this is a misconception. Create a new type, and
           | make it so that it's only produced by a function that checks
           | if the age is superior to 18.
        
             | CraigJPerry wrote:
             | That's a poor hack, it moves an invariant that could be
             | enforced at compile time to not much more than a convention
             | that has to be preserved by code review.
             | 
             | E.g. a colleague implements de-serialisation for your type
             | but adds an empty constructor to make their life easier.
             | You might not learn there's a hole in the boat before your
             | first bug.
        
               | Zababa wrote:
               | I wouldn't call it a poor hack. In a way it's a parser,
               | which aren't really poor hacks, but can be abused. Sure,
               | it's not perfect and I'd like it better if it was checked
               | at compile time, but it's way better than using a simple
               | int. Also, the moment you have a bug, tracking it is
               | really easy: just list the functions that returns this
               | specific type.
        
           | LeonidasXIV wrote:
           | Yes, but this can be easily worked around by creating custom
           | types that wrap the integer (and can be unwrapped on compile
           | time) and some conversion functions. So slightly more tedious
           | but saying one can't define complex constraints on static
           | types is not quite correct.
        
           | ajuc wrote:
           | Turbo Pascal could.
           | 
           | In practice the only benefit is very similar to checked
           | exceptions - can't forget to check that value is in range.
        
         | dharmaturtle wrote:
         | IMO the sentence is incorrect. Static type systems would
         | "enforce the stronger constraint" at run time... same as the
         | dynamic type system. Perhaps the dynamic type system can have a
         | fancy linter that does something crazy like running your
         | code... but I'm not aware of any such linter.
        
           | p_l wrote:
           | Most static type systems that can do what clojure.spec can do
           | tend to include runtime assertion and type checks and do not
           | erase type data from runtime (what some static type zealots
           | call "uni-type" approach).
           | 
           | For example Ada's type system, which has equivalent of Common
           | Lisp's _SATISFIES_ construct, which implements a runtime type
           | assert that can use all the power of the language.
        
             | dharmaturtle wrote:
             | Sorry, I don't understand
             | 
             | >Most static type systems that can do what clojure.spec can
             | do tend to include runtime assertion and type checks and do
             | not erase type data from runtime (what some static type
             | zealots call "uni-type" approach).
             | 
             | When you don't erase the type data... you're gonna have
             | more than one type. How is this a "uni-type" approach?
        
               | p_l wrote:
               | A popular (stupid) talking point in the stupid
               | discussions about static/dynamic while missing that the
               | axes were orthogonal, was for proponents of static types
               | to claim that dynamic languages were "unityped" based on
               | some convoluted logic about tagging data at runtime.
        
               | dharmaturtle wrote:
               | As someone who prefers static types, I agree with you
               | that claiming dynamic languages are "unityped" is stupid.
               | Javascript/Python/Clojure all literally have types.
        
           | tekacs wrote:
           | > Perhaps the dynamic type system can have a fancy linter
           | that does something crazy like running your code... but I'm
           | not aware of any such linter.
           | 
           | Names in Clojure codebases and libraries are pretty reliably
           | annotated with trailing exclamation marks, looking like:
           | `save!`.
           | 
           | To that end, running Clojure code blindly to test it and its
           | types is a fairly practical practice, coming up in cases like
           | Ghostwheel [1], which uses this for generative testing of
           | clojure.spec types, which can be much more sophisticated than
           | what is commonly used in static systems, even with refinement
           | types.
           | 
           | [1]: https://github.com/gnl/ghostwheel#staying-sane-with-
           | function...
        
             | dharmaturtle wrote:
             | >which can be much more sophisticated than what is commonly
             | used in static systems, even with refinement types
             | 
             | Can you elaborate on this? I have some experience with
             | Clojure, and have been relatively unimpressed with spec.
             | Everything it does I can do with (refinement) types (I
             | think). Reading over the Spec documentation it constantly
             | talks about predicates... which is exactly what a
             | refinement type is.
             | 
             | Spec/Clojure has the problem where it validates... but
             | doesn't parse. See: https://lexi-
             | lambda.github.io/blog/2019/11/05/parse-don-t-va...
        
         | divs1210 wrote:
         | You can have arbitrary types. Like a PrimeInteger type or a
         | NameStartingWithD type or a complex hashmap with types defined
         | for each key, like found in most configuration files.
        
       | jb1991 wrote:
       | I wonder why they are using Schema instead of Spec. Schema was
       | popular before the latter existed, I didn't realize anyone still
       | uses it.
        
         | ARandomerDude wrote:
         | I still prefer Schema because I find it more much more
         | readable.
        
           | synthc wrote:
           | yes i also prefer it. With Schema, the defined schema's are
           | normal vars, which you can jump to with editor support. With
           | spec i'm always searching for the definitions.
        
         | whalesalad wrote:
         | That is probably exactly why they are using it ... you don't
         | get to 250k lines of code overnight.
        
           | jb1991 wrote:
           | My understanding is that the company is a few years younger
           | than Spec, but perhaps the code base is very very old.
        
             | synthc wrote:
             | Schema is a good library. Everyone seems to use spec today,
             | but Schema easier to use than spec IMO.
             | 
             | Spec is also still in alpha, with spec2 still under
             | development.
        
       | fnord77 wrote:
       | we have a clojure codebase that's about 100k lines. Honestly I'm
       | kinda fed up with it. Certain 3rd party libs we've used have been
       | abandoned. We wrote our own libs for a major framework and it is
       | failing behind.
       | 
       | Too many "I'm very clever" functions that are hard to understand
       | and also have subtle bugs.
        
         | coneill wrote:
         | Seconded. I work in a clojure codebase that were trying to get
         | out of. There's just dead libraries everywhere and stuff that
         | maintained by one person that gets no updates at all. That or
         | we just end up making functional "wrappers" around Java
         | libraries and at that point we might as well just write
         | straight Java.
         | 
         | Also yea everyone wants to be so damn smart having macros
         | within macros within macros that no one knows what the original
         | intent of the code is anymore.
         | 
         | The repl based development I find also breeds a really bad
         | mentality of forgoing building a deployment process and instead
         | people just repl in and make a bunch of changes and prod rarely
         | matches whats checked into github.
        
           | raspasov wrote:
           | I've been using Clojure for almost 10 years and writing
           | macros has always been discouraged in the community. You
           | don't see too many of them in the wild, and for good reasons.
           | 
           | If you're writing macros on a daily basis, you better have a
           | really good reason for it.
        
         | oh-4-fucks-sake wrote:
         | Obligatory evangalism: Considered Kotlin as a JVM-lang-of-
         | choice? We use it on all our backends and we really love it.
        
         | raspasov wrote:
         | Are those "clever" functions pure?
        
         | dragandj wrote:
         | I wonder if they'd be abandoned if companies using them
         | considered these 3rd party libraries valuable enough to
         | contribute/fund the development.
        
         | ashes-of-sol wrote:
         | Would you mind sharing the abandoned libs/framework?
         | 
         | When you say you have too many "I'm clever" functions, do you
         | mean within code your team wrote, or in the ecosystem at large?
        
       | swamiji wrote:
       | this is wonderful - I've been curious about continuations and
       | program state as a language construct. Not sure I understand the
       | redplanet - type system et
        
       | mping wrote:
       | This is what I imagine experienced clojure developers can squeeze
       | out of a language like clojure. I would venture that they can
       | train a junior programmer in a couple of weeks, and make them
       | productive very fast.
       | 
       | I guess they could make it work in any language but judging by
       | the description clojure is indeed a great fit, due to the macro
       | capabilities, flexibility and solid runtime via JVM.
        
         | trutannus wrote:
         | > would venture that they can train a junior programmer in a
         | couple of weeks, and make them productive very fast
         | 
         | I've got _some_ experience with functional languages, and a
         | good amount with functional features in OO languages. Started
         | learning Clojure this week, and was pleasantly surprised with
         | how quickly I could get working projects up and running. Less
         | upfront learning curve than Elixir, which was unexpected.
        
         | tmountain wrote:
         | Yeah, it's nice to see such thoughtful adoption of language
         | facilities clearly oriented towards creating a successful and
         | maintainable codebase.
         | 
         | The Clojure team has always done a nice job expressing the
         | rationale for specific language features, and these rationales
         | often lean towards solving problems that system designers
         | historically faced.
         | 
         | Oftentimes, I think folks think of modern languages in regard
         | to their syntax, tooling, ergonomics, etc; however, to me, the
         | more interesting benefit in adopting a modern language is in
         | how its inbuilt features address design problems that earlier
         | generation languages exposed.
         | 
         | For a real world example of what I'm talking about, you can
         | google "clojure expression problem" and find compelling
         | articles about how Clojure solves this with protocols.
         | 
         | Providing a toolkit for attacking categories of problems
         | inherently gets people focused on the fact that these problems
         | exist in the first place when they may not even recognize them
         | otherwise, and regardless of the choice of language, it leads
         | to better design oriented thinking in the context of larger and
         | more complex systems.
        
           | doyougnu wrote:
           | Clojure doesn't solve the expression problem and the
           | expression problem tends to be trivial in dynamically typed
           | languages.
           | 
           | Strictly speaking the expression problem is only defined for
           | _static_ type systems because its concerned with _static_
           | type safety and extensions without recompilation.
        
             | tmountain wrote:
             | It does solve it and doing so was a key design goal of
             | protocols. It's right there in the docs:
             | 
             | There are several motivations for protocols:
             | 
             | Avoid the 'expression problem' by allowing independent
             | extension of the set of types, protocols, and
             | implementations of protocols on types, by different parties
             | 
             | I agree that it's a concern in static type systems, but the
             | same issue rears its head when defining methods which
             | operate on specific types of strongly typed data, so no,
             | it's not always trivial, nor is it a problem exclusive to
             | statically typed languages, and if it was, there wouldn't
             | be a need to introduce a new abstraction for which
             | addressing the problem is a key goal.
        
         | AtNightWeCode wrote:
         | What I heard from colleagues that work with Clojure is that it
         | is a horrible language where the default way of writing code is
         | an imperative programming style where contexts are passed
         | around and updated. Far from the concepts of functional
         | programming.
        
           | stingraycharles wrote:
           | I'm not sure I understand what you mean. I don't consider
           | Clojure to be imperative at all -- everything is immutable by
           | default, and you actually have to go through considerable
           | efforts to write things in an imperative style.
           | 
           | When I compare Clojure to another functional language I know
           | well, Haskell, one of the things I really feel it lacks is
           | proper pattern matching and currying; yes there are libraries
           | that you can use, but it's just not idiomatic to do in
           | Clojure. I would not, however, assert that it's an imperative
           | language.
           | 
           | Could you care to elaborate on what exactly you find is
           | lacking in Clojure?
        
             | davidrupp wrote:
             | It's not difficult to write imperative code in Clojure; [1]
             | is an example. Immutability-by-default makes you work to
             | mutate things, for sure, but that in itself doesn't make
             | the language inherently functional.
             | 
             | [1] https://clojuredocs.org/clojure.core/let#example-542692
             | c7c02...
        
               | robthethird wrote:
               | Your link might be pointing to the wrong example. I
               | assume you wanted to show an example of an atom.
        
           | divs1210 wrote:
           | What is this? Are you being serious?
           | 
           | I've worked in multiple Clojure shops and it has always been
           | amazing.
           | 
           | All the core code and business logic etc. (kernel) is
           | implemented functionally, and the interface with the outside
           | world (shell) is implemented imperatively.
           | 
           | Clojure being a horrible language is a really hot take and it
           | being based on hearsay makes your coomment look like it's not
           | in good faith.
        
             | AtNightWeCode wrote:
             | As with all functional programming languages, if you limit
             | the use to some specific areas you can get a lot done with
             | a few easily understood lines of code.
        
               | StreamBright wrote:
               | Quite the opposite. Functional style is especially useful
               | in larger codebases. However, I think a functional
               | strongly typed language is often easier to get right than
               | a weakly typed one. I mostly write F# and Clojure and
               | based on my experience I would go for F# any day over
               | Clojure but at the same time I would also go for Clojure
               | over Java as well.
               | 
               | I do not know where your views are coming from, sounds
               | like 2nd hand experience rather than 1st one.
        
               | AtNightWeCode wrote:
               | As I stated in the beginning I do not work with Clojure.
               | Pure functions is something I use in any programming
               | language. I have a hands on experience from a lot of
               | different functional languages though including F#. From
               | what I understand it is very common to use context based
               | style of coding in Clojure. That is at least what I heard
               | and Google does not seem to disagree...
        
               | [deleted]
        
           | ltultraweight wrote:
           | I use Clojure quite a bit and I don't write anything
           | imperatively.
           | 
           | But passing contexts I can see. There is a not uncommon
           | pattern that one can use of keeping a large map with state in
           | it.
           | 
           | However it's completely compatible with pure functional
           | programming.
        
         | cliftonk wrote:
         | I've found I typically reach for clojure when i need to do
         | something on the jvm and want a better java than java.
        
           | lukashrb wrote:
           | Interesting take since Clojure and Java are two very
           | different languages. And unlike for example Kotlin, Clojure
           | does not try to be a better Java than Java. But true though
           | Clojure leverages the power of the jvm.
        
             | __jem wrote:
             | Interop from Clojure -> Java is still incredibly easy,
             | though. I often will just wrap a Java library rather than
             | look for a pre-existing Clojure implementation because it's
             | so easy. Of course, most Java libs don't value immutability
             | and functional patterns, but you can still push this kind
             | of interop to the edges of your system and keep everything
             | else in pure Clojure.
        
               | StreamBright wrote:
               | Depends on the style the Java library you are trying to
               | use is written in. I have had mixed results. Especially
               | the Java 8 functional style had some challenges. I might
               | have the relevant SO question somewhere.
        
             | forgetfulness wrote:
             | It was a better Java in many ways.
             | 
             | The doto macro was a relief back in the days when Java APIs
             | insisted on being designed around stateful setters and
             | getters rather than the builder pattern, it allowed you to
             | operate on such unfortunately designed objects in single
             | logical blocks and simulating it all being an expression.
             | 
             | Proxy allowed you to instantiate anonymous inner classes
             | implementing only the methods of an interface that you
             | needed, the rest you could omit; in Java you have to put
             | them all, empty, which necessitated that you use an IDE to
             | generate them, and back then IDEs were not as nice as
             | today.
             | 
             | Those two alone made interacting with contemporary Java
             | libraries so much easier.
             | 
             | It also was convenient to go from Java collections to
             | Clojure collections and vice versa.
        
               | pjmlp wrote:
               | " We were not out to win over the Lisp programmers; we
               | were after the C++ programmers. We managed to drag a lot
               | of them about halfway to Lisp."
               | 
               | -- Guy Steele
        
             | pjmlp wrote:
             | Basically you get a new version of a Lisp Machine, that you
             | can sneak up in lots of places, whereas with Common Lisp it
             | isn't so easy to do so.
        
           | forgetfulness wrote:
           | I think that's also why Clojure use peaked right before Java
           | 8 was released; once Java became a better Java, and libraries
           | started to be designed around more ergonomic APIs than wiring
           | up objects that needed miles of stateful configuration code,
           | the pressure that drove you out of Java and into Clojure
           | began to diminish.
        
           | dustingetz wrote:
           | Clojure's value prop is:
           | 
           | 1) default immutability (same simple data structures used in
           | every library -> ecosystem composes better)
           | 
           | 2) portable code across multiple host platforms (jvm, node,
           | browser)
           | 
           | 3) metaprogramming
           | 
           | IMO, in 2007, immutability on the JVM was a competitive value
           | prop, but in 2021+ it is nothing special. It is the
           | combination of the three things which is a competitive value
           | prop today. Metaprogramming in particular is a mostly
           | unexplored frontier, because it is very hard to do well, and
           | very easy to do badly. Default immutability is kind of a
           | necessary starting point to do metaprogramming well.
        
             | davidrupp wrote:
             | > in 2007, immutability on the JVM was a competitive value
             | prop, but in 2021+ it is nothing special
             | 
             | Can you elaborate on this? What do you think has changed in
             | that time to make it "nothing special"?
        
               | dustingetz wrote:
               | immutability as a library is available in basically all
               | mainstream languages now and mainstream frameworks
               | leverage it (react, any UI framework, spark, any data
               | framework or database); JS vms are competitive with the
               | JVM and the JVM might even be losing ground in the cloud;
               | typescript is a monster and is letting people explore
               | haskell concepts in industry applications; scala is _way
               | better_ in 2021 than it was in 2011; every new PL can
               | compile to JS and supports immutability. Clojure 's sweet
               | spot is currently developing sub-languages embedded in
               | Clojure like RPL is doing (we are doing exactly the same
               | thing at hyperfiddle). That is still too hard to do in
               | typescript imo. Maybe it is possible in scala 3 but it
               | took them 10 years of pure autism to figure out how to do
               | monadic IO in scala due to how complex scala is, so i'd
               | expect it to take another 10 years to figure out how to
               | do metaprogramming in a commercially viable way, I'd be
               | happy to be proven wrong.
        
               | davidrupp wrote:
               | "10 years of pure autism" -- This made me smile.
        
               | [deleted]
        
       | misiti3780 wrote:
       | If I want to learn Clojure, where is the best place to start?
       | 
       | I have a lot of experience with Python/Javascript now, and spent
       | many years in C/C++/Objective C and Java. Also have some Go.
        
         | pkd wrote:
         | If you are into books, I will recommend Clojure for the Brave
         | and True which is free to read online [1], and Living Clojure
         | [2], in that order.
         | 
         | If you're into interactive kata-style problems, there's
         | 4Clojure [3].
         | 
         | Also join the Clojurians's Slack [4] for the community.
         | 
         | [1] https://www.braveclojure.com/clojure-for-the-brave-and-
         | true/ [2] https://www.oreilly.com/library/view/living-
         | clojure/97814919... [3] https://www.4clojure.com/ [4]
         | https://clojurians.slack.com/
        
         | ifFxhF938 wrote:
         | The "Clojure for the Brave and True" ebook is a popular and
         | free resource for beginners: https://www.braveclojure.com/
        
         | sremani wrote:
         | https://purelyfunctional.tv/mini-guide/the-ultimate-guide-to...
        
       | chromanoid wrote:
       | > One of the coolest parts of our codebase is the new general
       | purpose language at its foundation. Though the semantics of the
       | language are substantially different than Clojure, it's defined
       | entirely within Clojure using macros to express the differing
       | behavior. It compiles directly to bytecode using the ASM library.
       | The rest of our system is built using both this language and
       | vanilla Clojure, interoperating seamlessly.
       | 
       | Actually this sounds quite horrible.
        
         | afro88 wrote:
         | Imagine starting a job as a closure dev and being told you have
         | to learn a new language with substantially different semantics
        
           | TeMPOraL wrote:
           | Everyone is doing it already, to a large extent. Where "an
           | API" ends and "a programming language" starts is a matter of
           | opinion. It's a fuzzy boundary.
        
         | dustingetz wrote:
         | JSX would be an (early) example of this class of language. XML
         | is equivalent to s-expressions. So React.js (drunkly) is just a
         | reactive language embedded in a normal language with seamless
         | ability to hop between them. I elaborate about this equivalence
         | here
         | https://www.reddit.com/r/Clojure/comments/mavg81/the_distinc...
         | . The brilliance is they did this so smoothly that nobody
         | noticed, and with dynamic types, and without the typed people
         | even noticing!
        
           | TeMPOraL wrote:
           | The elephant in the room is Babel, which is essentially a
           | macro processor for JavaScript.
        
         | iamcreasy wrote:
         | I have dabbled in Clojure and Julia, and both of them support
         | macro to make it easier to develop DSL looking syntax. I have
         | always wondered how difficult it it to debug macro heavy code -
         | where's other language cite 'no macro' as a feature such as
         | Zig.
        
           | brundolf wrote:
           | I kind of like Rust's version: macros are limited and clunky
           | (and _really_ clunky if you want to do anything super fancy),
           | which means they 're available, but I only end up using them
           | when it's truly worth it
        
           | SatvikBeri wrote:
           | In my experience with Julia it's no easier or harder than any
           | other code. Macros tend to be pretty transparent, and it's
           | easy to see what code they generate, although I've never had
           | to do so. Most of the time, a library with a macro-based DSL
           | (e.g. JuMP) has a function-based DSL underlying it that's
           | just more verbose.
        
           | TeMPOraL wrote:
           | > _I have always wondered how difficult it it to debug macro
           | heavy code_
           | 
           | It isn't much harder than regular core. `macroexpand' is your
           | friend! Particularly when wrapped by your IDE[0] into a
           | "macrostepper" tool, which lets you macroexpand into an
           | overlay[1], step by step.
           | 
           | Ultimately, macros are just functions - if slightly peculiar
           | ones (they receive unevaluated arguments, and their return
           | values are treated as if they were the code at the point of
           | invocation). You can unit-test them just like any other
           | function.
           | 
           | That's not to say there aren't macros that are very tough to
           | debug. But the problem isn't _macro-heavy_ code, as in code
           | containing a lot of macros. The problem are _heavy macros_ -
           | monstrosities with complex internal logic, expanding their
           | inputs into large amount of code generation, hidden state,
           | etc. Complex macros like these take skill to write[2], but
           | they 're also rare to see.
           | 
           | --
           | 
           | [0] - By which I mean Emacs, though I guess there are Clojure
           | IDEs now too.
           | 
           | [1] - I.e. a read-only view replacing the code you're
           | macroexpanding on screen.
           | 
           | [2] - First rule: minimize the amount of code within the
           | actual macro definition, move all logic into functions to be
           | called by the macro, and unit test those extensively.
        
       | rst13 wrote:
       | This is sick
        
       ___________________________________________________________________
       (page generated 2021-06-03 23:00 UTC)