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