3 ms·
The more nightmarish thing about Clojure is realizing that, in truth, you have no idea what all these dicts you are passing around the terse, nil-punning functi
by forgetfulness 2y ago
The more nightmarish thing about Clojure is realizing that, in truth, you have no idea what all these dicts you are passing around the terse, nil-punning functions of your codebase hold at any given time.
- nogridbag 2y agoThat was the case for me. I went all in drinking the Clojure koolaid and wrote some small internal CLI tools with it. If I came back to that code a month or two later I could only properly understand it if I opened up a REPL to debug it. I ported those tools to Java and they were dead simple to comprehend.
- kamaal 2y agoAfter a while, lesser the magic, the better. One of the reasons Golang has had a use case in today's age is there is a need for a programming language with just functions, loops and if/else statements.
- koito17 2y agoYup. I learned Clojure just so I can use a Lisp and get paid for it, but there is some weird cult against all forms of typing. Even coming from a Common Lisp background, this was strange to me. In Common Lisp, there are implementations (like SBCL and ECL) that can make use of type declarations to produce efficient machine code and allow the compiler to catch errors that would otherwise be run-time errors. There's also other benefits like contextual autocomplete. The autocomplete in Clojure tooling is very basic, and many Clojure libraries try to make up for this by using qualified keywords everywhere. That way, rather than seeing all keywords ever interned, you can type ":some.namespace/" and your editor shows a dozen keys instead of hundreds of unrelated keys. Many in the Clojure community believe that occasionally validating maps against a schema "at the boundaries" is good enough. In practice, I have found this to be insufficient. Nearly every Clojure programmer I know has had to "chase nils" as a result of a map no longer including a key and several functions passing down a nil value until some function throws an exception. (Note: I don't specify which exception, because it depends on how that nil value gets used!) Refactoring Clojure code in general is a nightmare, and I suspect it is why many in the community are reluctant to change code in existing libraries and build entirely new things in parallel instead. Backwards compatibility is one often-cited reason, but I do think another reason is that refactoring Clojure code creates an endless game of bug fixing unless you have full test coverage of your codebase and use generative testing everywhere. (I've never seen a Clojure codebase with both of these things. I can count on one hand the number of Clojure codebases where generative testing is used at all). Function spec instrumentation provides something that feels like runtime type checks in Common Lisp, but now you have to manually run certain functions at the REPL just to ensure some change in your codebase did not introduce a type error. On the flip side, Java has things like DTOs which always felt too boilerplate-ish for me (though at least it provides useful names for endpoint data when generating Swagger/OpenAPI documentation). Even then, records in Java provide what are essentially maps with type safety and similar characteristics as DTOs. I think the structural typing offered by languages like OCaml and TypeScript provide exactly what I'd want in Clojure. But when faced with feature requests in Clojure, people will state something like "I have never had a use-case for X, therefore you don't need X". In the case of criticisms, the response is often "I may have ran into X before, but it's so rare that I don't consider it a problem".
- nogridbag 2y agoI still don't get how Java records can be used for anything like a DTO. Since you're a Clojure dev you may remember the pattern Rich Hickey described as "place oriented programming" :) Nearly every endpoint will have more than 2-3 fields and you really don't want a Java record with more than that many fields for the same reason you don't want a Java method with that many fields e.g. doIt(Long, String,String,Long,String,int,int,String) <-- code smell. And the problem I always see is something may start off as a Java record and then need to be refactored into a class as soon as 1-2 more fields are added.
- koito17 2y ago> I still don't get how Java records can be used for anything like a DTO In Clojure, we often deserialize a result set from a database to a vector of maps. These maps have different keys depending on what exactly your query was selecting. In Java, one often "projects" results to some DTO. This is one scenario where records offer identical functionality while avoiding boilerplate. Regarding "place-oriented programming", records are immutable, so that is one technical advantage they have over handwriting a DTO. And from my short experience using web frameworks like Quarkus, it seems that a lot of the "design patterns" I see in documentation exist to help design easy-to-test programs rather than unfettered mutation. Additionally, I have found records useful for describing the payload of endpoints that accept map-like data. Without records, I would be writing POJOs with public fields anyway. Overall, Java records behave like TypeScript interfaces with awkward syntax. I have found them ideal for expressing type-safe, map-like data with minimal boilerplate.
- nogridbag 2y agoI'm also using it for projections. But to be honest we have a fairly large Quarkus app and only use projections in a few places. For immutable classes with a large number of fields where instances have to be created manually I usually use the builder pattern. But the analogy to typescript interfaces is interesting.
- ghufran_syed 2y ago